6.5 Slices: Borrowing Portions of Data
Slices are references to a segment of a collection, allowing you to access parts of data without owning it or making unnecessary copies. This makes working with subsets of data efficient and safe.
6.5.1 String Slices
#![allow(unused)] fn main() { let s = String::from("hello world"); let hello = &s[0..5]; // "hello" let world = &s[6..11]; // "world" }
String slices (&str
) are references to a sequence of UTF-8 bytes within a String
. They allow you to work with parts of a string without taking ownership.
6.5.2 Array Slices
#![allow(unused)] fn main() { let arr = [1, 2, 3, 4, 5]; let slice = &arr[1..4]; // [2, 3, 4] }
6.5.3 Slices in Functions
Slices are commonly used in function parameters to allow functions to work with parts of data without taking ownership, making functions more flexible.
fn sum(slice: &[i32]) -> i32 { slice.iter().sum() } fn main() { let arr = [1, 2, 3, 4, 5]; // Passing a slice of the array (partial array) let partial_result = sum(&arr[1..4]); println!("Sum of slice is {}", partial_result); // Passing the whole array as a slice let total_result = sum(&arr); println!("Sum of entire array is {}", total_result); }
Explanation:
-
Function Definition:
- The
sum
function takes a slice ofi32
values (&[i32]
) and returns their sum. - The function operates on the slice without taking ownership, allowing it to accept any segment of an array or vector.
- The
-
In
main
:- We define an array
arr
containing five integers. - Passing a Partial Slice:
- We pass a slice of the array to
sum
using&arr[1..4]
, which includes elements at indices 1 to 3 (2, 3, 4
). - The
partial_result
calculates the sum of this slice.
- We pass a slice of the array to
- Passing the Whole Array:
- We pass the entire array to
sum
using&arr
without specifying a range. - The
total_result
calculates the sum of all elements in the array.
- We pass the entire array to
- We define an array
By using slices, functions can operate on data without taking ownership, allowing them to accept both entire arrays and portions of arrays seamlessly.
Benefits:
- Flexibility: The same function can operate on both full arrays and subarrays without any modification.
- Efficiency: Since slices are references, they avoid unnecessary copying of data.
- Safety: Rust ensures that slices do not outlive the data they reference, preventing dangling references.
Additional Example with String Slices:
fn print_slice(slice: &str) { println!("Slice: {}", slice); } fn main() { let s = String::from("hello world"); // Passing a substring print_slice(&s[0..5]); // Outputs: Slice: hello // Passing the whole string print_slice(&s); // Outputs: Slice: hello world }
Key Takeaways:
-
Passing the Whole Collection:
- You can pass the entire array or string to a function expecting a slice by referencing it with
&arr
or&s
.
- You can pass the entire array or string to a function expecting a slice by referencing it with
-
Automatic Coercion:
- Rust automatically coerces arrays and strings to slices when you pass them by reference to functions expecting slices.
-
No Need for Full Range Specification:
- Specifying the full range like
&arr[0..arr.len()]
is unnecessary;&arr
suffices.
- Specifying the full range like
Comparison with C
In C, you use pointers and manual length management:
#include <stdio.h>
void sum(int *slice, int length) {
int total = 0;
for(int i = 0; i < length; i++) {
total += slice[i];
}
printf("Sum is %d\n", total);
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
sum(&arr[1], 3);
return 0;
}
C does not perform bounds checking, whereas Rust slices include length information and are bounds-checked at runtime.