Rilasciato · in miglioramento
Guida a Rust · 6/6
Per ora questo capitolo è disponibile solo in inglese.
Rust guarantees memory safety without a garbage collector through ownership. Once these rules click, most compiler errors read like helpful advice. The chapter ends with traits and generics for reusable code.
There are three rules:
Assigning a heap-backed value such as String, or passing it to a function, moves ownership and the original variable becomes unusable. Call clone() for a deep copy. Small types like integers, bool and char implement Copy and are copied instead.
fn take(s: String) {
println!("got: {s}");
} // s is dropped here
fn main() {
let a = String::from("hello");
let b = a; // ownership moves from a to b
// println!("{a}"); // compile error: value used after move
let c = b.clone(); // deep copy, including the heap data
take(b); // ownership of b moves into the function
println!("{c}");
let x = 5;
let y = x; // i32 is Copy, so this is a plain copy
println!("{x} {y}");
}Moving ownership on every call would be tedious, so Rust lets you borrow a value through a reference. &T is a shared, read-only reference and &mut T is an exclusive, mutable one. The borrowing rules:
&T) may exist at the same time.&mut T) must be the only reference while it is alive.fn len_of(s: &str) -> usize {
s.len()
}
fn add_world(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
let n = len_of(&s); // only borrowed, so s is still usable
add_world(&mut s);
println!("{s} ({n})");
let r1 = &s;
let r2 = &s;
println!("{r1} {r2}");
// r1 and r2 are no longer used, so a mutable borrow is allowed
let r3 = &mut s;
r3.push('!');
println!("{s}");
}This prevents data races at compile time. Holding let first = &v[0]; and then calling v.push(4) is rejected, because the push may reallocate and leave first dangling.
A lifetime is the span of code where a reference is valid. Elision rules let the compiler infer most of them, but when a function takes several references and returns one, you name the relationship with a parameter like 'a. Annotations never extend a value's life; they only describe how references relate.
// The result is valid for as long as the shorter of a and b
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
// A struct that holds a reference needs a lifetime parameter too
struct Excerpt<'a> {
part: &'a str,
}
fn main() {
let text = String::from("First sentence. Second sentence.");
let first = text.split('.').next().unwrap_or("");
let excerpt = Excerpt { part: first };
println!("{}", excerpt.part);
let a = String::from("long string");
{
let b = String::from("xyz");
let result = longest(a.as_str(), b.as_str());
println!("{result}"); // used only while b is alive
}
}The special lifetime 'static means "valid for the whole program"; string literals are the classic example.
A trait defines behavior a type can provide, much like an interface, optionally with default methods. Generic functions use trait bounds such as T: PartialOrd to accept any type with that behavior. Generics are monomorphized at compile time, so they cost nothing at runtime.
trait Summary {
fn author(&self) -> String;
// Default body, used unless a type overrides it
fn summarize(&self) -> String {
format!("(a post by {})", self.author())
}
}
struct Post {
title: String,
user: String,
}
struct Tweet {
user: String,
}
impl Summary for Post {
fn author(&self) -> String {
self.user.clone()
}
fn summarize(&self) -> String {
format!("{} - {}", self.title, self.user)
}
}
impl Summary for Tweet {
fn author(&self) -> String {
format!("@{}", self.user)
}
}
fn notify(item: &impl Summary) {
println!("breaking: {}", item.summarize());
}
fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
let mut max = items[0];
for &item in items {
if item > max {
max = item;
}
}
max
}
fn main() {
let post = Post { title: "Learning Rust".to_string(), user: "kim".to_string() };
let tweet = Tweet { user: "lee".to_string() };
notify(&post);
notify(&tweet);
println!("{}", largest(&[3, 9, 2])); // 9
// Different types in one list need trait objects (dyn)
let feed: Vec<Box<dyn Summary>> = vec![Box::new(post), Box::new(tweet)];
for item in &feed {
println!("{}", item.summarize());
}
}Many standard traits can be derived with #[derive(...)]. Generics and impl Trait are resolved at compile time; dyn Trait dispatches at runtime.
0 commenti
Accedi · Accedi per lasciare un commento.
Scrivi tu il primo commento.