借入チェック

初心者向けの最も一般的な質問の1つは、「借入を確認するにはどうすればよいですか?」です。 借用検証は、Rust学習曲線の中で最もクールな部分の1つであり、初心者がこの概念をプログラムに適用するのが難しいことは明らかです。







ごく最近、 Rust subreditで、 「借入検証と戦わない方法のヒント」という質問が表示されました







Rustコミュニティの多くのメンバーが、このテストに関連する手間を回避するための有用なヒントを提供しています。Rustでコードを設計する方法にヒントを与えるヒントです(ヒント:Javaでの方法ではありません)。







この投稿では、一般的なトラップの疑似実例をいくつか示します。







最初に、借用をチェックするためのルールを要約します。







  1. 一度に1つの可変変数参照しか持てません
  2. 必要な数の変更不可能な変数参照を持つことができます
  3. 単一の変数に可変参照と不変参照を混在させることはできません


長寿命ブロック



なぜなら 一度に変更可能な借用は1つだけであるため、1つの関数で何かを2回変更したい場合に問題が発生する可能性があります。 借用が交差しなくても、借用小切手は文句を言います。







コンパイルされない例を見てください。







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   //       //  }
      
      





ここでの問題は、可変のジルの借用があることであり、それを使用して名前を印刷しようとします。 状況を修正すると、借入の範囲を制限するのに役立ちます。







 fn main() { let mut jill = Person::new("Jill", 19); { let jill_ref_mut = &mut jill; jill_ref_mut.celebrate_birthday(); } println!("{}", jill.name()); }
      
      





一般に、可変リンクの可視性を制限することをお勧めします。 これにより、上記のような問題を回避できます。







コールチェーン



多くの場合、ローカル変数の数を減らして割り当てを許可するために、関数呼び出しを連鎖させます。 PersonおよびName構造を公開するライブラリがあると想像してください。 人の名前への可変リンクを取得して更新する必要があります。







 #[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(); //   //     }
      
      





ここでの問題は、 Person :: nameが変数を参照するのではなく所有権を返すことです。 Name :: first_nameを使用してリンクを取得しようとすると、借用チェックでエラーが発生します。 ブロックが完了するとすぐに、 jill.name()から返された値は削除され、 nameはぶら下がりポインタになります。







解決策は、一時変数を入力することです。







 fn main() { let name = Name::new("Jill", "Johnson"); let mut jill = Person::new(name, 20); let name = jill.name(); let name = name.first_name(); }
      
      





良い方法では、 Person :: nameから&Nameを返す必要がありますが、値の所有権を返すことが唯一の妥当なオプションである場合がいくつかあります。 これが発生した場合、コードを修正する方法を知っておくといいでしょう。







円形リンク



コード内で循環参照に出くわすことがあります。 これは、Cでプログラミングするときに頻繁に使用したものです。 Rustで借用のチェックに苦労して、このようなコードがどれほど危険かを示しました。







クラスとそれらに記録された生徒のプレゼンテーションを作成しましょう。 レッスンでは生徒を参照し、生徒は出席するクラスへの参照を保持します。







 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); }
      
      





コードをコンパイルしようとすると、エラーメッセージが殺到します。 主な問題は、学生と募集のクラスへのリンクを維持しようとしていることです。 変数が(逆の順序で)削除されると、 教師も削除されますが、 ジルジャックは 、削除するレッスンを引き続き参照します。







最も簡単な(しかし読みにくい)ソリューションは、借用検証を避け、 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); }
      
      





借用テストが提供するセキュリティ保証はもうないことに注意してください。







/ u / steveklabnik1が指摘したように、 引用







RcとRefCellは両方ともランタイムセキュリティメカニズムに依存していることに注意してください。 コンパイル時のチェックが失われます。たとえば、borrow_mutを2回呼び出そうとすると、RefCellはパニックします。

おそらく、最良のオプションは、循環参照が不要になるようにコードを再編成することです。







データベース内の関係を正規化したことがある場合、これは同様のケースです。 生徒とレッスンの間のリンクは別の構造に保ちます。







 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); }
      
      





いずれにせよ、このアプローチの方が優れています。 受講者が受講しているクラスに関する情報を保持する理由はなく、レッスンには受講者に関する情報を保存しないでください。 この情報が必要な場合は、訪問リストから取得できます。







結論として



借用を検証するためのルールがなぜそうなのかまだ理解できない場合は、redditユーザー/ u / Fylwindのこの説明が役立ちます。 彼は、読み取り/書き込みロックのアナロジーを著しく引用しました







ロックシステム(読み取り/書き込みロック)として検証を借用することを想像します。 不変のリンクがある場合、オブジェクトの共有ロックとして表示されます。可変リンクがある場合、これはすでに一種の排他ロックです。 また、他のロックシステムと同様に、必要以上に長くロックを保持するのは悪い考えです。 これは、可変リンクにとって特に悪いです。

最後に、一見したところ、借用の検証に苦労しているように思われる場合、それを使用することを学ぶとすぐにそれを愛するでしょう。








All Articles