Parameter Passing
Here's something cool about Coco: you don't have to think about whether to pass by value or by reference. The compiler figures it out for you based on the type.
The Smart Default
Coco's rule is simple:
- Small types (integers, floats, booleans) → passed by value (copied)
- Large types (strings, arrays, structs) → passed by reference automatically
fn process_number($x i32) {
// $x is a copy - modifications don't affect the original
$x += 1;
echo $x;
}
fn process_text($text string) {
// $text is automatically a reference
// Efficient even for huge strings
echo $text.len();
}
You write the same syntax, but the compiler does the right thing. No performance penalty for passing large data, no surprise mutations.
Why This Design?
In most languages, you have to choose:
- Pass by value — Safe (no aliasing), but expensive for large data
- Pass by reference — Efficient, but you can accidentally mutate shared data
Coco's approach gives you both: efficiency by default, safety through the borrow checker.
fn analyze($data Dataset) {
// $data is passed by reference automatically
// But it's immutable unless we ask for &mut
$total = $data.sum();
$average = $total / $data.len();
// ...
}
$my_data = load_dataset("big_file.csv");
analyze($my_data);
// $my_data is unchanged
Explicit References
Sometimes you need explicit control. Use & for immutable references and & with mutation:
// Explicitly take a reference
fn count_words($text &string): i32 {
return $text.split(" ").len();
}
// Take a mutable reference
fn append_suffix($text &string, $suffix string) {
$text = $text + $suffix;
}
$message = "Hello";
append_suffix(&$message, "!");
echo $message; // Hello!
Ownership Transfer
When you want to take ownership (consume the value), use move:
fn consume($data move Vec<i32>) {
// $data is moved into this function
// Caller can no longer use it
process_and_free($data);
}
$numbers = vec![1, 2, 3];
consume($numbers);
// $numbers is no longer valid here
This is useful when:
- The function will free the resource
- You want to prevent the caller from using the data afterward
- You're transferring ownership to another data structure
Copy vs Move Semantics
Types in Coco are either Copy or Move:
Copy types (small, stack-allocated):
- Integers, floats, booleans, chars
- Passed by value, always copied
Move types (heap-allocated or large):
- Strings, vectors, structs with heap data
- Passed by reference by default
- Can be explicitly moved
// i32 is Copy
$a = 5;
$b = $a; // $a is copied
echo $a; // Still valid
// String is Move
$s1 = "hello";
$s2 = $s1; // $s1 is moved
// echo $s1; // Error: $s1 was moved
The Borrow Checker
Coco's borrow checker enforces these rules at compile time:
- One mutable reference OR any number of immutable references
- References must be valid (no dangling pointers)
$data = vec![1, 2, 3];
// Multiple immutable borrows - OK
$a = &$data;
$b = &$data;
echo $a.len(), $b.len();
// Mutable borrow - can't have other borrows
$c = &$data; // Mutable
// let $d = &$data; // Error: can't borrow while mutably borrowed
$c.push(4);
This catches data races at compile time.
Parameter Modes Summary
| Syntax | Mode | Use When |
|---|---|---|
$x Type |
Auto (value or ref) | Default - let compiler decide |
$x &Type |
Immutable reference | Need explicit reference |
$x &Type (mut) |
Mutable reference | Need to modify the value |
$x move Type |
Ownership transfer | Consuming the value |
Returning Values
The same principles apply to return values:
// Small values returned by value (copied)
fn calculate(): i32 {
return 42;
}
// Large values use move semantics
fn build_report(): Report {
$report = Report::new();
// ... build it ...
return $report; // Moved out, not copied
}
Returning large values is efficient—no copying happens.
Zero-Copy Views
For cases where you need to look at data without copying:
fn first_word($text &string): &string {
// Return a reference to part of the input
$end = $text.find(' ').unwrap_or($text.len());
return &$text[0..$end];
}
$sentence = "Hello World";
$word = first_word(&$sentence);
echo $word; // Hello
The returned reference is valid as long as $sentence is valid.
Best Practices
Let the compiler decide. Use the simple $x Type syntax unless you have a specific reason for explicit references.
Use mutable references sparingly. Prefer returning new values over mutating in place.
Think about ownership. If a function needs to keep data, it should take ownership. If it just needs to look at it, take a reference.
Document ownership transfer. If a function consumes its input, make that clear in the name or docs.
// Clear from the name that this takes ownership
fn into_bytes($s move string): Vec<u8> {
// ...
}