raku.gg / beginner

Subroutines and Signatures

2026-03-12

Raku's subroutine system is where the language really starts to shine. While you can write simple functions just like in any other language, Raku's signatures give you type checking, multiple dispatch, and pattern matching right in the function declaration. Let us start with the basics and build up.

Declaring a Subroutine

The simplest subroutine takes no parameters and returns nothing special:
sub greet { say "Hello, World!"; } greet; # Hello, World! greet(); # also works with parens
Both calling styles work. Parentheses are optional when there is no ambiguity.

Positional Parameters

Positional parameters are listed in the signature after the sub name:
sub greet($name) { say "Hello, $name!"; } greet("Alice"); # Hello, Alice!
Multiple positional parameters:
sub add($a, $b) { return $a + $b; } say add(3, 7); # 10
The return keyword is optional. The last expression in the sub is the return value:
sub add($a, $b) { $a + $b; } say add(3, 7); # 10

Named Parameters

Named parameters start with a colon in the signature and are passed with the same colon syntax:
sub create-user(:$name, :$email) { say "Creating user: $name ($email)"; } create-user(name => "Alice", email => "alice@example.com"); # or using colon-pair syntax: create-user(:name<;Alice>;, :email<;alice@example.com>;);
Named parameters can be passed in any order:
create-user(email => "bob@test.com", name => "Bob"); # Creating user: Bob (bob@test.com)

Mixing Positional and Named

sub send-message($text, :$to, :$urgent = False) { my $prefix = $urgent ?? "[URGENT] " !! ""; say "$prefix$text -> $to"; } send-message("Hello", to => "Alice"); # Hello -> Alice send-message("Server down!", to => "Admin", urgent => True); # [URGENT] Server down! -> Admin

Optional Parameters

Positional parameters are required by default. To make one optional, add a ?:
sub greet($name, $greeting?) { my $g = $greeting // "Hello"; say "$g, $name!"; } greet("Alice"); # Hello, Alice! greet("Alice", "Bonjour"); # Bonjour, Alice!
Named parameters are optional by default. To make one required, add !:
sub login(:$username!, :$password!) { say "Logging in $username..."; } login(username => "admin", password => "secret"); # login(username => "admin"); # ERROR: required named parameter 'password' not passed

Default Values

Both positional and named parameters can have defaults:
sub connect($host = "localhost", :$port = 8080, :$ssl = False) { my $protocol = $ssl ?? "https" !! "http"; say "Connecting to $protocol://$host:$port"; } connect; # http://localhost:8080 connect("example.com"); # http://example.com:8080 connect("example.com", port => 443, ssl => True); # https://example.com:443

Type Constraints

You can specify the expected type for each parameter:
sub add(Int $a, Int $b --> Int) { $a + $b; } say add(3, 7); # 10 # say add(3, "7"); # ERROR: Type check failed for parameter '$b'
The --> arrow specifies the return type.

Common type constraints:

sub process(Str $text) { ... } # must be a string sub calculate(Num $x) { ... } # must be a floating point number sub count(Int $n) { ... } # must be an integer sub check(Bool $flag) { ... } # must be True or False sub handle(Any $thing) { ... } # anything goes (this is the default)

Subset Types

You can create custom type constraints with subset:
subset PositiveInt of Int where * >; 0; subset NonEmpty of Str where *.chars >; 0; sub repeat(Str $text, PositiveInt $times) { say $text x $times; } repeat("ha", 3); # hahaha # repeat("ha", -1); # ERROR: Type check failed

The Return Type Arrow

You can declare what type a sub returns:
sub is-even(Int $n --> Bool) { $n %% 2; } say is-even(4); # True say is-even(7); # False

Multi Subs

Here is where Raku gets really interesting. multi sub lets you define multiple versions of the same function, each with a different signature. Raku picks the right one at call time:
multi sub greet(Str $name) { say "Hello, $name!"; } multi sub greet(Int $count) { say "Hello to $count people!"; } multi sub greet { say "Hello, stranger!"; } greet("Alice"); # Hello, Alice! greet(42); # Hello to 42 people! greet; # Hello, stranger!
Multi dispatch works on types, number of arguments, and even value constraints:
multi sub fizzbuzz(Int $n where * %% 15) { "FizzBuzz" } multi sub fizzbuzz(Int $n where * %% 3) { "Fizz" } multi sub fizzbuzz(Int $n where * %% 5) { "Buzz" } multi sub fizzbuzz(Int $n) { $n.Str } for 1..20 -> $n { say fizzbuzz($n); }
This is the classic FizzBuzz problem solved with multi dispatch. Raku tries each candidate in order of specificity (the where clauses are more specific than the plain Int match).

Multi Dispatch with Destructuring

You can match on literal values:
multi sub factorial(0) { 1 } multi sub factorial(Int $n where * >; 0) { $n * factorial($n - 1); } say factorial(5); # 120 say factorial(10); # 3628800
This reads almost like a mathematical definition.

Slurpy Parameters

When you want a sub to accept any number of arguments, use a slurpy parameter:
# Slurpy positional (array) sub sum(*@numbers) { @numbers.sum; } say sum(1, 2, 3, 4, 5); # 15 say sum(10, 20); # 30
# Slurpy named (hash) sub log-event($message, *%details) { say $message; for %details.kv -> $k, $v { say " $k: $v"; } } log-event("User login", user => "alice", ip => "192.168.1.1"); # User login # user: alice # ip: 192.168.1.1

Passing Arrays and Hashes

When you pass an array to a function, it flattens into the positional parameters by default. To pass it as a single argument, use a backslash or the $ sigil:
sub show-list(@items) { say "Items: @items.join(', ')"; } my @fruits = <;apple banana cherry>;; show-list(@fruits); # Items: apple, banana, cherry
For hashes:
sub show-config(%conf) { for %conf.sort -> (:$key, :$value) { say "$key = $value"; } } my %settings = port => 8080, host => "localhost"; show-config(%settings);

Anonymous Subs and Blocks

You can create anonymous subroutines and store them in variables:
my &;doubler = sub ($n) { $n * 2 }; say doubler(21); # 42 # Or more concisely with a block: my &;tripler = -> $n { $n * 3 }; say tripler(14); # 42 # Or with WhateverCode: my &;quadrupler = * * 4; say quadrupler(10); # 40
Blocks with -> $param are called pointy blocks. They are everywhere in Raku (loops, map, grep, etc.).

Practical Example

Let us put everything together in a practical example:
#!/usr/bin/env raku # Type-constrained helper sub validate-age(Int $age --> Bool) { 0 <; $age <; 150; } # Multi sub for formatting multi sub format-entry(Str :$name!, Int :$age! where &;validate-age) { "$name (age $age)"; } multi sub format-entry(Str :$name!) { "$name (age unknown)"; } # Slurpy collector sub build-roster(Str $team, *@members) { say "Team: $team"; say "=" x 30; for @members.kv -> $i, $member { say " { $i + 1 }. $member"; } say "Total: { @members.elems } members\n"; } # Use multi dispatch my @entries = ( format-entry(name => "Alice", age => 30), format-entry(name => "Bob", age => 25), format-entry(name => "Charlie"), ); build-roster("Engineering", |@entries);
Output:
Team: Engineering ============================== 1. Alice (age 30) 2. Bob (age 25) 3. Charlie (age unknown) Total: 3 members

What is Next?

Now that you can write powerful, type-safe subroutines with multi dispatch, we will turn our attention to one of the most common tasks in programming: working with strings and text. Next up: string processing in Raku.