Meta-Object Protocol
2026-04-02
Raku's Meta-Object Protocol (MOP) lets you inspect and manipulate classes, roles, and objects at runtime. Using the .^ twigil (pronounced "dot-hat"), you can query an object's methods, attributes, parents, roles, and more. This is introspection on steroids.
The .^ Twigil
The .^ twigil invokes a method on the object's metaclass rather than the object itself:
class Dog {
has $.name;
has $.breed;
method bark() { say "Woof!" }
method fetch($item) { say "Fetches $item" }
}
my $d = Dog.new(name => "Rex", breed => "Lab");
say $d.^name;
say $d.^methods;
say $d.^attributes;
Listing Methods
.^methods returns all methods defined on a type:
class Calculator {
method add($a, $b) { $a + $b }
method sub($a, $b) { $a - $b }
method mul($a, $b) { $a * $b }
method div($a, $b) { $a / $b }
}
say Calculator.^methods(:local).map(*.name);
say Calculator.^methods.map(*.name).sort;
The :local flag restricts to methods defined in this class only.
Inspecting Attributes
.^attributes reveals an object's internal structure:
class Person {
has Str $.name;
has Int $.age;
has Str $.email;
has @.hobbies;
}
for Person.^attributes(:local) -> $attr {
say "Name: {$attr.name}";
say " Type: {$attr.type.^name}";
say " Has accessor: {$attr.has_accessor}";
say "";
}
Output:
Name: $!name
Type: Str
Has accessor: True
Name: $!age
Type: Int
Has accessor: True
Name: $!email
Type: Str
Has accessor: True
Name: @!hobbies
Type: Positional
Has accessor: True
Reading Attribute Values
You can read attribute values on an instance through the MOP:
class Config {
has $.host = 'localhost';
has $.port = 8080;
has $.debug = False;
}
my $c = Config.new;
for $c.^attributes(:local) -> $attr {
say "{$attr.name}: {$attr.get_value($c)}";
}
Inspecting the Class Hierarchy
class Animal { }
class Mammal is Animal { }
class Dog is Mammal { }
say Dog.^parents;
say Dog.^parents(:all);
say Dog.^mro;
say Dog.^isa(Animal);
say Dog.^isa(Mammal);
say Dog.^isa(Int);
Inspecting Roles
role Serializable { method serialize() { ... } }
role Loggable { method log($msg) { say $msg } }
class User does Serializable does Loggable {
has $.name;
method serialize() { "User: $!name" }
}
say User.^roles;
say User.^roles(:local);
say User.^does(Serializable);
say User.^does(Loggable);
Dynamic Method Calls
Use .^find_method to look up methods dynamically:
class Greeter {
method hello() { say "Hello!" }
method goodbye() { say "Goodbye!" }
}
my $g = Greeter.new;
my $method-name = 'hello';
if my $m = $g.^find_method($method-name) {
$m($g);
}
$g."$method-name"();
Building a Generic Serializer
Here is a practical use of the MOP to build a serializer that works on any class:
sub to-hash($obj --> Hash) {
my %data;
for $obj.^attributes(:local) -> $attr {
my $name = $attr.name.substr(2);
my $value = $attr.get_value($obj);
%data{$name} = $value;
}
%data
}
sub to-json-simple($obj --> Str) {
my %h = to-hash($obj);
'{' ~ %h.map({ "\"{.key}\": \"{.value}\"" }).join(', ') ~ '}'
}
class Product {
has $.name;
has $.price;
has $.sku;
}
my $p = Product.new(name => "Widget", price => 9.99, sku => "WDG-001");
say to-json-simple($p);
Building a Diff Tool
Compare two objects of the same type:
sub diff($a, $b) {
die "Type mismatch" unless $a.^name eq $b.^name;
my @changes;
for $a.^attributes(:local) -> $attr {
my $name = $attr.name.substr(2);
my $va = $attr.get_value($a);
my $vb = $attr.get_value($b);
if $va ne $vb {
@changes.push("$name: '$va' -> '$vb'");
}
}
@changes
}
class Server {
has $.host;
has $.port;
has $.status;
}
my $before = Server.new(host => "web1", port => 80, status => "up");
my $after = Server.new(host => "web1", port => 8080, status => "down");
say "Changes:";
.say for diff($before, $after);
Checking Method Signatures
You can inspect method parameters:
class API {
method get(Str $path, :$timeout = 30) { }
method post(Str $path, Hash $body, :$auth) { }
}
for API.^methods(:local) -> $m {
say "{$m.name}:";
for $m.signature.params -> $p {
next if $p.invocant;
say " {$p.name // 'anon'}: {$p.type.^name} (optional: {$p.optional})";
}
}
Runtime Type Creation
The MOP can even create new types at runtime:
my $new-class = Metamodel::ClassHOW.new_type(name => 'DynamicClass');
$new-class.^add_attribute(Attribute.new(
:name('$!value'),
:type(Str),
:package($new-class),
:has_accessor,
));
$new-class.^add_method('describe', method () {
say "I am {self.^name} with value: {self.value}";
});
$new-class.^compose;
my $obj = $new-class.new(value => "dynamic!");
$obj.describe;
The MOP is a powerful tool for building frameworks, ORMs, serializers, and any code that needs to work generically across different types. Use it when static typing is not enough and you need your code to adapt to whatever types it encounters.