Understanding Ownership in Rust with?Examples

Understanding Ownership in Rust with?Examples

The Rust programming language offers a unique approach to memory management, combining aspects of both automatic and manual memory management systems. This is achieved through an " ownership " system with rules that the compiler checks at compile time. This article will introduce you to the concept of ownership in Rust with detailed examples.

What is Ownership?

In Rust, the ownership concept is a set of rules that applies to all values. These rules dictate that each value in Rust has the following:

  1. A variable called its "owner".
  2. Only one owner at a time.
  3. When the owner goes out of scope, the value will be dropped.

This system exists primarily to make memory safe, eliminating common bugs such as null pointer dereferencing, double-free errors, and dangling pointers.

The Rules of Ownership

Variable Scope

The first key concept in Rust ownership is "scope." A scope is a range within a program for which a variable is valid. Here's an example:

{
    let s = "Hello, world!";
    // s is valid here
} // s is no longer valid past this point        

In this case, s is valid from the point at which it's declared until the closing brace of its scope.

The String?Type

For a more complex example, let's use the String type:

{
    let mut s = String::from("Hello, world!");
    s.push_str(", nice to meet you.");
    // s is valid and has changed
} // s is no longer valid past this point        

This case works similarly to the previous example, but we're also able to modify s. This results from the String type stored on the heap and can have a dynamic size.

Memory and Allocation

Regarding handling the String type, Rust automatically takes care of memory allocation and deallocation. In the example above, when s goes out of scope, Rust automatically calls the drop function, which returns the memory taken s back to the operating system.

Ownership and Functions

The ownership rules apply when interacting with functions as well. When a variable is passed to a function, the ownership of that variable is moved to the function (known as a "move"). After the move, the original variable can no longer be used.

fn main() {
    let s = String::from("hello");  // s comes into scope
    takes_ownership(s);             // s's value moves into the function
                                    // ... and so is no longer valid here
    //println!("{}", s);             // this line would lead to a compile error
}

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The memory is freed.        

In the example above, the println! line after the takes_ownership function call would result in a compile error because the ownership of s was moved to the takes_ownership function.

Borrowing and References

To allow access to data without taking ownership, Rust uses a concept called "borrowing." Instead of passing objects directly, we pass references to them.

fn main() {
    let s = String::from("hello"); // s comes into scope

    does_not_take_ownership(&s); // s's value is referenced here
                                 // s is still valid here
    println!("{}", s);           // this line will compile and print "hello"
}

fn does_not_take_ownership(some_string: &String) { // some_string is a reference
    println!("{}", some_string);
} // Here, some_string goes out of scope. But because it does not have ownership, nothing happens.        

In this case, &s creates a reference to the value of s but does not own it. Because it does not have ownership, the value it points to will not be dropped when the reference goes out of scope.

Note, however, that references are immutable by default. If you want to modify the borrowed value, you need to use a mutable reference with the mut keyword.

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}        

The Slice?Type

Another aspect of ownership in Rust involves the "slice" type. A slice is a reference to a contiguous sequence within a collection rather than the whole collection. Here's an example with a string slice:

fn main() {
    let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];
}        

In this example, hello will be a slice that references the first five bytes of s, and world will be a slice that references the following five bytes.

Rust's ownership model is a powerful tool for managing memory safety without a garbage collector. This ownership system with rules for borrowing and slicing allows for fine-grained control over memory allocation and deallocation, all while keeping the code safe from memory bugs and maintaining high performance.

Deep and Shallow?Copying

Understanding the idea of deep and shallow copying is essential in understanding ownership in Rust.

Let's start with a scenario. If we have a simple type, such as an integer, and we assign its value to a new variable, the value is copied, as seen here:

let x = 5;
let y = x;

println!("x = {}, y = {}", x, y);  // Outputs: "x = 5, y = 5"        

This is because integers are simple values with a known, fixed size and are stored on the stack. Therefore, the number is copied into the new variable. This type of copying is known as "deep copying."

However, things get more complex when we deal with data stored on the heap, like a String:

let s1 = String::from("hello");
let s2 = s1;

// println!("{}, world!", s1); // This line will cause an error        

This will throw a compile error because Rust prevents you from using s1 after transferring its ownership to s2. This default behaviour is known as "shallow copying" or "moving." If a deep copy is needed, you need to call the clone method:

let s1 = String::from("hello");
let s2 = s1.clone();

println!("s1 = {}, s2 = {}", s1, s2);  // Outputs: "s1 = hello, s2 = hello"        

Now, s1 and s2 are two separate strings with the same value, "hello".

The Copy?Trait

Rust has a special trait called Copy for handling cases where you want a value to be able to make a copy of itself. Simple data types like integers, booleans, floating point numbers, and character types have this trait. However, any type that requires allocation or is some form of resource, like String, does not have this trait.

If we have a type and we want to make it Copy, we can do so by adding an annotation to the type definition:

#[derive(Copy, Clone)]
struct Simple {
    a: i32,
    b: i32,
}

let s = Simple { a: 5, b: 6 };
let _t = s;

println!("{}", s.a);  // Outputs: "5"        

Conclusions

Ownership is a central feature of Rust, aiming to make memory management safe and efficient. This unique approach provides the best of both worlds: memory safety without needing a garbage collector. It enforces rules at compile-time, preventing a wide range of common programming errors related to memory use. However, it also requires a slightly different mindset when designing your Rust programs since you must always be mindful of who owns data at any time.

Stay tuned, and happy coding!

Visit my Blog for more articles, news, and software engineering stuff!

Follow me on Medium, LinkedIn, and Twitter.

Check out my most recent book — Application Security: A Quick Reference to the Building Blocks of Secure Software.

All the best,

Luis Soares

CTO | Head of Engineering | Blockchain Engineer | Solidity | Rust | Smart Contracts | Web3 | Cyber Security

#blockchain #rust #memory #safety #solana #smartcontracts #network #datastructures #data #smartcontracts #web3 #security #privacy #confidentiality #cryptography #softwareengineering #softwaredevelopment #coding #software

要查看或添加评论,请登录

Luis Soares的更多文章

  • Dynamic Linking and Memory Relocations in?Rust

    Dynamic Linking and Memory Relocations in?Rust

    When you compile source code into object files (such as files), the compiler generates machine code along with metadata…

  • Building an Error Correction System in?Rust

    Building an Error Correction System in?Rust

    Error correction is a key component of communication and data storage systems. Techniques like Reed-Solomon error…

  • Free Rust eBook – My Gift to You + New Blog

    Free Rust eBook – My Gift to You + New Blog

    ?? Thank You for 10,000 Followers! ?? I’m incredibly grateful to have reached this milestone of 10,000 followers here…

    8 条评论
  • Rust Lifetimes Made?Simple

    Rust Lifetimes Made?Simple

    ?? Rust lifetimes are one of the language’s most powerful and intimidating features. They exist to ensure that…

    5 条评论
  • Zero-Knowledge Proof First Steps - New Video!

    Zero-Knowledge Proof First Steps - New Video!

    In today’s video, we’re diving straight into hands-on ZK proofs for Blockchain transactions! ??? Whether you’re new to…

    1 条评论
  • Your Next Big Leap Starts Here

    Your Next Big Leap Starts Here

    A mentor is often the difference between good and great. Many of the world’s most successful personalities and industry…

    8 条评论
  • Building a VM with Native ZK Proof Generation in?Rust

    Building a VM with Native ZK Proof Generation in?Rust

    In this article we will build a cryptographic virtual machine (VM) in Rust, inspired by the TinyRAM model, using a…

    1 条评论
  • Understanding Pinning in?Rust

    Understanding Pinning in?Rust

    Pinning in Rust is an essential concept for scenarios where certain values in memory must remain in a fixed location…

    10 条评论
  • Inline Assembly in?Rust

    Inline Assembly in?Rust

    Inline assembly in Rust, specifically with the macro, allows developers to insert assembly language instructions…

    1 条评论
  • Building a Threshold Cryptography Library in?Rust

    Building a Threshold Cryptography Library in?Rust

    Threshold cryptography allows secure splitting of a secret into multiple pieces, called “shares.” Using a technique…

    2 条评论

社区洞察

其他会员也浏览了