References
References allow you to borrow values without taking ownership. Klar provides two types of references: ref for read-only access and inout for mutable access.
Read-Only References (ref)
Use ref to borrow a value without being able to modify it:
fn print_length(ref s: string) -> void {
println("Length: {s.len()}")
// Cannot modify s here
}
fn main() -> i32 {
let greeting: string = "hello"
print_length(ref greeting) // Borrow greeting
println(greeting) // greeting is still valid
return 0
}Passing ref Parameters
When calling a function with ref parameter, use ref at the call site:
fn analyze(ref data: [i32]) -> i32 {
var sum: i32 = 0
for x: i32 in data {
sum = sum + x
}
return sum
}
fn main() -> i32 {
let numbers: [i32; 5] = [1, 2, 3, 4, 5]
let total: i32 = analyze(ref numbers) // Explicit ref at call site
return total
}Multiple ref Borrows
Multiple ref borrows are allowed simultaneously:
fn compare(ref a: string, ref b: string) -> bool {
return a == b
}
fn main() -> i32 {
let s: string = "hello"
let same: bool = compare(ref s, ref s) // OK - multiple ref borrows
return 0
}Mutable References (inout)
Use inout to borrow a value with the ability to modify it:
fn double_in_place(inout n: i32) -> void {
n = n * 2
}
fn main() -> i32 {
var x: i32 = 10
double_in_place(inout x) // x is now 20
return x
}Passing inout Parameters
Use inout at both the call site and the function signature:
fn sort_in_place(inout arr: [i32]) -> void {
// Sort the array...
}
fn main() -> i32 {
var numbers: [i32; 5] = [5, 3, 1, 4, 2]
sort_in_place(inout numbers) // Explicit inout at call site
// numbers is now sorted
return 0
}Variable Must Be Mutable
You can only take an inout reference to a var:
fn increment(inout n: i32) -> void {
n = n + 1
}
fn main() -> i32 {
let x: i32 = 10
// increment(inout x) // Error: cannot take inout of immutable variable
var y: i32 = 10
increment(inout y) // OK
return y
}Exclusive Access
Only one inout reference is allowed at a time:
fn swap(inout a: i32, inout b: i32) -> void {
let temp: i32 = a
a = b
b = temp
}
fn main() -> i32 {
var x: i32 = 10
var y: i32 = 20
swap(inout x, inout y) // OK - different variables
// swap(inout x, inout x) // Error: cannot borrow x mutably twice
return 0
}Methods with References
ref self - Read-Only Methods
struct Point {
x: i32,
y: i32,
}
impl Point {
fn magnitude(ref self: Point) -> f64 {
let x: f64 = self.x.as#[f64]
let y: f64 = self.y.as#[f64]
return sqrt(x * x + y * y)
}
fn to_string(ref self: Point) -> string {
return "({self.x}, {self.y})"
}
}inout self - Mutating Methods
impl Point {
fn scale(inout self: Point, factor: i32) -> void {
self.x = self.x * factor
self.y = self.y * factor
}
fn reset(inout self: Point) -> void {
self.x = 0
self.y = 0
}
}
fn main() -> i32 {
var p: Point = Point { x: 10, y: 20 }
p.scale(2) // inout self is implicit for method calls
return p.x // 20
}Reference Rules
- ref borrows don't require the source to be mutable
- inout borrows require the source to be a
var - Multiple ref borrows are allowed simultaneously
- Only one inout borrow is allowed at a time
- ref and inout cannot coexist for the same value
fn main() -> i32 {
var x: i32 = 10
// OK: multiple ref
let r1: ref i32 = ref x
let r2: ref i32 = ref x
// OK: single inout
let m1: inout i32 = inout x
// Error: ref and inout together
// let r3: ref i32 = ref x
// let m2: inout i32 = inout x
return 0
}When to Use Each
Use ref When
- You only need to read the value
- Multiple parts of code need access simultaneously
- The function is a "query" that doesn't change state
fn contains(ref list: List#[i32], target: i32) -> bool {
for x: i32 in list {
if x == target {
return true
}
}
return false
}Use inout When
- You need to modify the value
- The function updates state in place
- You want to avoid copying large values
fn append_all(inout list: List#[i32], items: [i32]) -> void {
for x: i32 in items {
list.push(x)
}
}Use Ownership When
- You need full control of the value
- The function "consumes" the value
- You're building/transforming data
fn uppercase(s: string) -> string {
return s.to_uppercase() // Takes ownership, returns new string
}Example: In-Place Algorithms
fn reverse_in_place(inout arr: [i32]) -> void {
var left: i32 = 0
var right: i32 = arr.len() - 1
while left < right {
let temp: i32 = arr[left]
arr[left] = arr[right]
arr[right] = temp
left = left + 1
right = right - 1
}
}
fn main() -> i32 {
var numbers: [i32; 5] = [1, 2, 3, 4, 5]
reverse_in_place(inout numbers)
// numbers is now [5, 4, 3, 2, 1]
return numbers[0] // 5
}Example: Accumulator Pattern
struct Stats {
count: i32,
sum: i32,
min: ?i32,
max: ?i32,
}
impl Stats {
fn new() -> Stats {
return Stats { count: 0, sum: 0, min: None, max: None }
}
fn add(inout self: Stats, value: i32) -> void {
self.count = self.count + 1
self.sum = self.sum + value
match self.min {
Some(m) => {
if value < m { self.min = Some(value) }
}
None => { self.min = Some(value) }
}
match self.max {
Some(m) => {
if value > m { self.max = Some(value) }
}
None => { self.max = Some(value) }
}
}
fn average(ref self: Stats) -> f64 {
if self.count == 0 {
return 0.0
}
return self.sum.as#[f64] / self.count.as#[f64]
}
}Next Steps
- Ownership - Ownership model basics
- Reference Counting - Shared ownership
- Structs - Methods with self parameters