Objective
What is Reference and Slice in Rust Programming?
By the end of this lesson, you will understand how to use references and slices in Rust. You will learn the difference between borrowing with references, how to create slices for arrays and strings, and advanced techniques for using references and slices effectively.
1. References
In Rust, references allow you to access data without taking ownership. A reference is indicated by the &
symbol. Rust enforces rules around references to ensure memory safety.
Example: Immutable References
fn main() {
let s1 = String::from("Hello, Rust!");
let len = calculate_length(&s1); // Borrowing s1 with an immutable reference
println!("Length of '{}' is {}.", s1, len); // s1 can still be used
}
fn calculate_length(s: &String) -> usize {
s.len() // s is an immutable reference to the original String
}
In this example, s1
is borrowed immutably by the calculate_length
function, allowing it to be used later in main
.
2. Mutable References
If you need to modify the borrowed data, you can create a mutable reference using &mut
. However, Rust enforces that only one mutable reference exists at a time to prevent data races.
Example: Mutable References
fn main() {
let mut s1 = String::from("Hello");
change(&mut s1); // Pass a mutable reference to s1
println!("{}", s1); // s1 is now modified
}
fn change(s: &mut String) {
s.push_str(", Rust!"); // Modifies the borrowed String
}
Attempting to create a mutable reference while an immutable reference exists will result in a compile-time error.
3. Slices
Slices provide a way to reference a contiguous sequence of elements in a collection, such as an array or a string, without taking ownership. A slice is defined using a range.
Example: String Slices
fn main() {
let s = String::from("Hello, Rust!");
let hello = &s[0..5]; // Slicing the string to get "Hello"
println!("Slice: {}", hello); // Outputs: Slice: Hello
}
Example: Array Slices
You can also create slices for arrays.
fn main() {
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..4]; // Slice contains [2, 3, 4]
println!("Array Slice: {:?}", slice); // Outputs: Array Slice: [2, 3, 4]
}
4. Complex Example: Function with Slices
You can define functions that accept slices as parameters, allowing for flexible data handling.
Example: Function with Array Slice
fn main() {
let arr = [10, 20, 30, 40, 50];
let result = sum(&arr[1..4]); // Passing a slice of the array
println!("Sum of slice: {}", result); // Outputs: Sum of slice: 90
}
fn sum(slice: &[i32]) -> i32 {
slice.iter().sum() // Sums the elements in the slice
}
5. Advanced Example: Slices and Strings
Strings in Rust are also represented as slices. You can use string slices to handle portions of strings efficiently.
Example: Function with String Slices
fn main() {
let s = String::from("Hello, Rust!");
let slice = first_word(&s);
println!("First word: {}", slice); // Outputs: First word: Hello
}
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes(); // Convert to bytes
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' { // Find the first space
return &s[0..i]; // Return the slice up to the space
}
}
&s[..] // If no space found, return the entire string
}
In this example, the first_word
function returns a string slice representing the first word in the given string.
6. Conclusion
In this lesson, you learned about references and slices in Rust. You explored how to create immutable and mutable references, how to work with string and array slices, and how to define functions that accept slices as parameters. These concepts are essential for efficient and safe data manipulation in Rust, allowing you to work with data without transferring ownership.