Now that you can borrow strings and slices safely, it’s time to group several values under one type. Rust’s struct lets you name related pieces of data, and an [[impl|impl block]] defines the behavior that belongs to that data. In this part we’ll craft a small study log struct and confirm that immutable vs mutable reference rules apply to methods exactly the same way.
Defining a Struct
struct StudyLog {
topic: String,
completed: bool,
notes: Vec<String>,
}
fn main() {
let mut log = StudyLog {
topic: String::from("borrowing"),
completed: false,
notes: vec![String::from("Review immutable references")],
};
println!("{} in progress", log.topic);
log.notes.push(String::from("Write a mutable reference example"));
println!("Note count: {}", log.notes.len());
}
Struct fields are private by default. This example lives in one module so we can access fields directly, but production code usually exposes methods to read or mutate fields.
impl Blocks and Methods
When you define methods, the first parameter is one of self, &self, or &mut self. Each option mirrors ownership rules:
- &self: borrow immutably, read only
- &mut self: borrow mutably, exclusive write access
- self: take ownership of the instance
impl StudyLog {
fn new(topic: &str) -> Self {
Self {
topic: topic.to_string(),
completed: false,
notes: Vec::new(),
}
}
fn add_note(&mut self, memo: &str) {
self.notes.push(memo.to_string());
}
fn summary(&self) -> String {
format!(
"{} / done: {} / {} notes",
self.topic,
self.completed,
self.notes.len(),
)
}
fn finish(self) -> StudyLog {
StudyLog {
completed: true,
..self
}
}
}
fn main() {
let mut log = StudyLog::new("strings");
log.add_note("Separate String and &str");
log.add_note("Safe slice ranges");
println!("{}", log.summary());
let finished = log.finish();
// println!("{}", log.summary()); // compile error: log moved into finish
println!("Done state: {}", finished.summary());
}
newreturns an ownedStudyLog.add_noteneeds&mut self, so no other references can be active during the call.summaryonly reads, so&selfis enough.finishconsumesself, so the caller can’t use the old value afterward.
Reusing Slices and References Inside Methods
Bringing the string concepts into methods looks like this:
impl StudyLog {
fn latest_note(&self) -> Option<&str> {
self.notes.last().map(|note| note.as_str())
}
}
fn main() {
let mut log = StudyLog::new("structs");
log.add_note("Initialize fields");
log.add_note("Build an impl block");
if let Some(text) = log.latest_note() {
println!("Latest note: {}", text);
}
}
Option<&str> lets the struct keep ownership of each note while lending slices to callers. Instead of cloning strings, you return "Some(&str) if it exists, None otherwise" and let the borrow checker enforce safety.
Practice in CodeSandbox
The sandbox below uses CodeSandbox's Rust starter. Move the main code into src/main.rs, then compare cargo check and cargo run so you can read the compiler feedback beside the final output.
💬 댓글
이 글에 대한 의견을 남겨주세요