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.
Try It Yourself
- Implement
mark_done(&mut self)to togglecompleted, then observe what happens if you also hold an immutable reference while calling it. - Add a
minutesfield that tracks study time and write a method that computes the average. - Experiment with passing the struct by
self,&self, and&mut selfto see which one communicates intent most clearly.
Wrap-Up
Structs and methods give Rust code context. Each field declares ownership explicitly, and methods mirror the borrowing rules so you always know who owns what. Up next we’ll represent richer state with enums and pattern matching, and wire Option into our structs.
💬 댓글
이 글에 대한 의견을 남겨주세요