raku.gg / metaprogramming

Roles and Mixins

2026-03-21

Roles are Raku's answer to the limitations of single inheritance. They let you compose reusable behavior into classes without the fragile hierarchies that come with deep inheritance trees. If you have used traits in Rust or interfaces in Java, roles will feel familiar, but they are more powerful than either.

Defining a Role

A role is declared with the role keyword:
role Printable { method print-info() { say "I am a {self.^name}"; } } class Document does Printable { has $.title; } my $doc = Document.new(title => "Report"); $doc.print-info; # I am a Document
The does keyword composes a role into a class. The class gains all the methods defined in the role.

Roles vs Classes

The key difference: roles are composed (flattened into the class), while classes are inherited (forming a chain). This means:
role Swimmer { method move() { say "Swimming" } } role Runner { method move() { say "Running" } } # This fails at compile time -- conflict detected! # class Triathlete does Swimmer does Runner { } # You must resolve the conflict explicitly: class Triathlete does Swimmer does Runner { method move() { say "Swimming and running" } }
This is much safer than multiple inheritance, where conflicts might silently pick the wrong method.

Roles with Attributes

Roles can define attributes that get composed into the class:
role Timestamped { has DateTime $.created = DateTime.now; has DateTime $.modified is rw = DateTime.now; method touch() { $!modified = DateTime.now; } } class Article does Timestamped { has $.title; has $.body; } my $a = Article.new(title => "Hello", body => "World"); say $a.created; $a.touch; say $a.modified;

Required Methods (Stubs)

A role can require that consuming classes implement certain methods:
role Serializable { method to-json() { ... } # Stub -- must be implemented method to-yaml() { ... } # Stub -- must be implemented method serialize(Str $format) { given $format { when 'json' { self.to-json } when 'yaml' { self.to-yaml } } } } class User does Serializable { has $.name; has $.email; method to-json() { "\{ \"name\": \"$!name\", \"email\": \"$!email\" \}" } method to-yaml() { "name: $!name\nemail: $!email" } } my $u = User.new(name => "Alice", email => "alice@example.com"); say $u.serialize('json'); say $u.serialize('yaml');
The ... (yada yada) marks a method as a stub. If a class does the role without implementing the stub, you get a compile-time error.

Parameterized Roles

Roles can accept parameters, making them generic:
role Container[::T] { has T @.items; method add(T $item) { @!items.push($item); } method get(Int $index --> T) { @!items[$index] } } class IntBox does Container[Int] { } class StrBox does Container[Str] { } my $ints = IntBox.new; $ints.add(42); $ints.add(99); say $ints.get(0); # 42 my $strs = StrBox.new; $strs.add("hello"); say $strs.get(0); # hello

Runtime Mixins with does and but

You can mix roles into individual objects at runtime:
role Labeled { has $.label is rw; } my $x = 42 but Labeled; $x.label = "The Answer"; say "$x is labeled: {$x.label}"; # 42 is labeled: The Answer
The but operator creates a copy of the value with the role mixed in. The original type is preserved:
say $x.^name; # Int+{Labeled} say $x + 8; # 50 -- still works as an Int
You can also mixin at object creation time with does:
role Auditable { has @.audit-log; method log-action(Str $action) { @!audit-log.push("$(DateTime.now): $action"); } } class Account { has $.balance is rw = 0; } my $acct = Account.new; $acct does Auditable; $acct.log-action("Account created"); $acct.balance = 100; $acct.log-action("Deposit: 100"); say $acct.audit-log;

Composing Multiple Roles

A class can compose any number of roles:
role Identifiable { has $.id = (1..999999).pick; } role Taggable { has @.tags; method add-tag(Str $tag) { @!tags.push($tag) } } role Cacheable { has $.cached = False; method cache() { say "Caching {self.^name}" } } class Product does Identifiable does Taggable does Cacheable { has $.name; has $.price; } my $p = Product.new(name => "Widget", price => 9.99); $p.add-tag("sale"); $p.add-tag("new"); $p.cache; say "#{$p.id}: {$p.name} [{$p.tags.join(', ')}]";

Roles as Type Constraints

Roles work as type constraints, which makes them useful for polymorphism:
role Drawable { method draw() { ... } } class Circle does Drawable { method draw() { say "Drawing circle" } } class Square does Drawable { method draw() { say "Drawing square" } } sub render(Drawable $shape) { $shape.draw; } render(Circle.new); # Drawing circle render(Square.new); # Drawing square
Roles are one of Raku's best features for building modular, composable systems. Use them liberally to share behavior across classes without the pitfalls of deep inheritance.