Sling Academy
Home/Rust/Ownership Strategies in Function Calls: Move, Borrow, Copy

Ownership Strategies in Function Calls: Move, Borrow, Copy

Last updated: January 03, 2025

In Rust programming, understanding ownership and how it interacts with function calls is pivotal to writing efficient and safe code. Rust provides mechanisms like move, borrow, and copy to handle data ownership when dealing with functions. This article will explore these concepts in detail, using practical code examples to highlight their usage and differences.

Move Semantics

In Rust, move semantics occur when ownership of a variable is transferred from one scope to another. When a value is moved, the original variable becomes invalid and cannot be used unless explicitly returned back.

fn main() {
    let s1 = String::from("Hello, world!");
    takes_ownership(s1);
    // s1 is no longer valid here, it has been moved
    // println!("s1: {}", s1); // This line would cause a compile error
}

fn takes_ownership(some_string: String) {
    println!("some_string: {}", some_string);
} // some_string is dropped here

In the above example, the function takes_ownership takes ownership of the String when it's called. Post function call, s1 is no longer valid in main and any attempt to use it will result in a compile-time error.

Borrow Semantics

Borrowing allows a function to use a value without taking ownership, enabling function calls without the need for value duplication or ownership transfer. Borrowing can be mutable or immutable depending on the required functionality.

fn main() {
    let s1 = String::from("Hello, borrow!");
    let len = calculate_length(&s1); // Passing a reference
    println!("The length of '{}' is {}.", s1, len);

    let mut s2 = String::from("Hello, mutable borrow!");
    change(&mut s2);
    println!("Changed s2: {}", s2);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

fn change(some_string: &mut String) {
    some_string.push_str(" This is changed.");
}

In the calculate_length function, we borrow s1 immutably, allowing the function to read the string without moving the ownership. Conversely, change lets us mutate s2 via a mutable borrow.

Copy Semantics

Copy semantics in Rust involves duplicating values that are stored on the stack. Scalars like integers, floats, and char types are automatically copied if they are subject to a variable assignment or passed to a function, as there is no ownership transfer involved.

fn main() {
    let x = 5;
    makes_copy(x);
    println!("x is still valid here: {}", x); // x is still usable since it's a Copy type
}

fn makes_copy(some_integer: i32) {
    println!("some_integer: {}", some_integer);
}

Here, makes_copy receives the integer x. Since integers implement the Copy trait, there's no move, and x continues to be valid after the function call.

Conclusion

By understanding and choosing the appropriate ownership semantics – move, borrow, or copy – developers can optimize Rust program handling for efficiency and safety. Moving involves ownership transfer, borrowing allows temporary usage without ownership change, and copying works for some simple data types that don't require transfer of any ownership. Mastery of these elements equips developers to write more robust and error-free Rust code.

Next Article: Migrating Synchronous Code to async Functions in Rust

Previous Article: Refactoring Methods to Standalone Functions and Vice Versa in Rust

Series: Working with Functions in Rust

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior