One of the most common questions for newbies is “How can I please credit verification?”. Borrowing testing is one of the coolest parts of the Rust learning curve and, of course, beginners have difficulty applying this concept in their programs.
Only recently, the question “Tips on how not to fight the borrowing test?” Appeared on the Rust loan .
Many members of the Rust community brought useful tips on how to avoid the troubles associated with this test, tips that shed light on how you should design your code on Rust (hint: not the way you do it in Java).
In this post I will try to show a few pseudo-real examples of common traps.
To begin with, I summarize the rules for checking borrowing:
Since you can only have one variable borrowing at a time, then you can get problems if you want to change something twice in one function. Even if the borrowing does not intersect, the borrowing check will complain.
Look at an example that does not compile.
struct Person { name: String, age: u8, } impl Person { fn new(name: &str, age: u8) -> Person { Person { name: name.into(), age: age, } } fn celebrate_birthday(&mut self) { self.age += 1; println!("{} is now {} years old!", self.name, self.age); } fn name(&self) -> &str { &self.name } } fn main() { let mut jill = Person::new("Jill", 19); let jill_ref_mut = &mut jill; jill_ref_mut.celebrate_birthday(); println!("{}", jill.name()); // jill // // }
The problem here is that we have a variable jill borrowing and then we again try to use it to print the name. Restrict the situation will help limit the scope of borrowing.
fn main() { let mut jill = Person::new("Jill", 19); { let jill_ref_mut = &mut jill; jill_ref_mut.celebrate_birthday(); } println!("{}", jill.name()); }
In general, it is a good idea to limit the scope of its variable links. This avoids problems similar to the one shown above.
You often want to concatenate function calls to reduce the number of local variables and let-assignments. Imagine that you have a library that leverages the Person and Name structures. You want to receive a changeable link to the name of the person and update it.
#[derive(Clone)] struct Name { first: String, last: String, } impl Name { fn new(first: &str, last: &str) -> Name { Name { first: first.into(), last: last.into(), } } fn first_name(&self) -> &str { &self.first } } struct Person { name: Name, age: u8, } impl Person { fn new(name: Name, age: u8) -> Person { Person { name: name, age: age, } } fn name(&self) -> Name { self.name.clone() } } fn main() { let name = Name::new("Jill", "Johnson"); let mut jill = Person::new(name, 20); let name = jill.name().first_name(); // // }
The problem here is that Person :: name returns ownership of the variable instead of referring to it. If we are trying to get a link using Name :: first_name , then the borrowing check will complain. As soon as the block is completed, the value returned from jill.name () will be deleted and the name will be a dangling pointer.
The solution is to enter a temporary variable.
fn main() { let name = Name::new("Jill", "Johnson"); let mut jill = Person::new(name, 20); let name = jill.name(); let name = name.first_name(); }
In a good way, we have to return the & Name from Person :: name , but there are several cases in which returning ownership of a value is the only reasonable option. If this happens, then it would be good to know how to fix your code.
Sometimes you come across circular references in your code. This is what I have used too often in C programming. The fight against checking borrowing in Rust showed me how dangerous such code could be.
Create a presentation of classes and students enrolled in them. The lesson refers to the students, and the students, in turn, retain references to the classes they attend.
struct Person<'a> { name: String, classes: Vec<&'a Class<'a>>, } impl<'a> Person<'a> { fn new(name: &str) -> Person<'a> { Person { name: name.into(), classes: Vec::new(), } } } struct Class<'a> { pupils: Vec<&'a Person<'a>>, teacher: &'a Person<'a>, } impl<'a> Class<'a> { fn new(teacher: &'a Person<'a>) -> Class<'a> { Class { pupils: Vec::new(), teacher: teacher, } } fn add_pupil(&'a mut self, pupil: &'a mut Person<'a>) { pupil.classes.push(self); self.pupils.push(pupil); } } fn main() { let jack = Person::new("Jack"); let jill = Person::new("Jill"); let teacher = Person::new("John"); let mut borrow_chk_class = Class::new(&teacher); borrow_chk_class.add_pupil(&mut jack); borrow_chk_class.add_pupil(&mut jill); }
If we try to compile the code, we will be bombarded with error messages. The main problem is that we are trying to keep references to lessons from students and a lot of things. When variables are deleted (in the reverse order of creation), the teacher will also be deleted, but jill and jack will still refer to the activity that should be deleted.
The simplest (but difficult to read) solution is to avoid checking borrowing and use Rc <RefCell> .
use std::rc::Rc; use std::cell::RefCell; struct Person { name: String, classes: Vec<Rc<RefCell<Class>>>, } impl Person { fn new(name: &str) -> Person { Person { name: name.into(), classes: Vec::new(), } } } struct Class { pupils: Vec<Rc<RefCell<Person>>>, teacher: Rc<RefCell<Person>>, } impl Class { fn new(teacher: Rc<RefCell<Person>>) -> Class { Class { pupils: Vec::new(), teacher: teacher.clone(), } } fn pupils_mut(&mut self) -> &mut Vec<Rc<RefCell<Person>>> { &mut self.pupils } fn add_pupil(class: Rc<RefCell<Class>>, pupil: Rc<RefCell<Person>>) { pupil.borrow_mut().classes.push(class.clone()); class.borrow_mut().pupils_mut().push(pupil); } } fn main() { let jack = Rc::new(RefCell::new(Person::new("Jack"))); let jill = Rc::new(RefCell::new(Person::new("Jill"))); let teacher = Rc::new(RefCell::new(Person::new("John"))); let mut borrow_chk_class = Rc::new(RefCell::new(Class::new(teacher))); Class::add_pupil(borrow_chk_class.clone(), jack); Class::add_pupil(borrow_chk_class, jill); }
Note that now we have no guarantees of security, which gives a check of borrowing.
As indicated / u / steveklabnik1, quote :
Note that Rc and RefCell both rely on a security mechanism at runtime, i.e. we lose compile-time checks: for example, RefCell will panic if we try to call borrow_mut twice.
Perhaps the best option would be to reorganize the code in such a way that circular references are not required.
If you have ever normalized relationships in a database, then this is a similar case. We will keep the links between the student and the lesson in a separate structure.
struct Enrollment<'a> { person: &'a Person, class: &'a Class<'a>, } impl<'a> Enrollment<'a> { fn new(person: &'a Person, class: &'a Class<'a>) -> Enrollment<'a> { Enrollment { person: person, class: class, } } } struct Person { name: String, } impl Person { fn new(name: &str) -> Person { Person { name: name.into(), } } } struct Class<'a> { teacher: &'a Person, } impl<'a> Class<'a> { fn new(teacher: &'a Person) -> Class<'a> { Class { teacher: teacher, } } } struct School<'a> { enrollments: Vec<Enrollment<'a>>, } impl<'a> School<'a> { fn new() -> School<'a> { School { enrollments: Vec::new(), } } fn enroll(&mut self, pupil: &'a Person, class: &'a Class) { self.enrollments.push(Enrollment::new(pupil, class)); } } fn main() { let jack = Person::new("Jack"); let jill = Person::new("Jill"); let teacher = Person::new("John"); let borrow_chk_class = Class::new(&teacher); let mut school = School::new(); school.enroll(&jack, &borrow_chk_class); school.enroll(&jill, &borrow_chk_class); }
In any case, this approach is better. There is no reason for the student to keep information about what classes he attends and the class itself should not contain information about who attends it. If this information is needed, it can be obtained from the list of visits.
If you still do not understand why the rules for checking borrowing are as they are, then this explanation of the reddit user / u / Fylwind can help. He remarkably cited an analogy with a read-write lock :
I think of borrowing as a blocking system (read-write lock). If you have an immutable link, then it appears as a shared lock on the object, in case you have a changeable link, then this is already an exclusive lock. And, as with any lock system, holding the lock longer than necessary is a bad idea. This is especially bad for mutable links.
Ultimately, if at first glance it seems to you that you are struggling with the verification of borrowing, then you will love it as soon as you learn how to use it.
Source: https://habr.com/ru/post/319808/
All Articles