rust - How do I upgrade a reference to a mutable reference? -
i have program revolves around 1 shared data structure, proposing changes data, , applying these changes @ later stage. these proposed changes hold references core object.
in c++ or language, make reference non-const, mutate when need to. rust doesn't play approach. (i asked in irc earlier today, sadly i'm still stuck.)
to help, made distilled example booking tickets in theatre, theatre data structure, bookings proposed changes, , run
method applying them if figure out how work!
firstly, defining data structures. theatre has many rows, have many seats each:
use std::sync::{arc, rwlock}; use std::thread; struct theatre { rows: vec<row> } struct row { seats: vec<seat> } struct seat { number: i32, booked: bool, } impl seat { fn new(number: i32) -> seat { seat { number: number, booked: false } } fn book(&mut self) { self.booked = true; } }
here, get_booking
method searches seat, returning booking
reference seat finds.
impl theatre { fn get_booking<'t>(&'t self, number: i32) -> option<booking<'t>> { row in self.rows.iter() { seat in row.seats.iter() { if seat.number == number && seat.booked == false { return some(booking { seats: vec![ seat ] }) } } } none } }
but stuck. run
method has mutable access overall theatre (from parameter), , knows seat mutate (self
). since self
isn't mutable, though theatre contains is, can't mutated.
struct booking<'t> { seats: vec<&'t seat> } impl<'t> booking<'t> { fn describe(&self) { let seats: vec<_> = self.seats.iter().map(|s| s.number).collect(); println!("you want book seats: {:?}", seats); } fn run(&self, _theatre: &mut theatre) { let mut seat = ??????; seat.book(); } }
finally, main method use if worked.
fn main() { // build theatre (with 1 seat... small theatre) let theatre = theatre { rows: vec![ row { seats: vec![ seat::new(7) ] } ] }; let wrapper = arc::new(rwlock::new(theatre)); // try book seat in thread let thread = thread::spawn(move || { let desired_seat_number = 7; let t = wrapper.read().unwrap(); let booking = t.get_booking(desired_seat_number).expect("no such seat!"); booking.describe(); let mut tt = wrapper.write().unwrap(); booking.run(&mut tt); // never reached because still have read lock }); thread.join().unwrap(); }
what's annoying know why current code doesn't work - can't figure out how rust wants program formatted instead. there things don't want do:
- the simplest solution have
booking
hold index seat, instead of reference: in case,row
,seat
usize
fields. however, although theatre uses o(1) vectors, i'd reference value in middle of large tree, having iterate find value more expensive. mean couldn't, say, seat number (indescribe
function) without having pass in entire theatre. - it solved having
booking
hold mutable reference seat, mutate normal. however, mean have 1 proposed change @ time: couldn't, example, have list of bookings , apply them @ once, or have 2 bookings , apply one.
i feel i'm close having rust accept, don't quite know how structure program accommodate it. so, pointers? (pun intended)
first, here's code:
use std::sync::{arc, rwlock}; use std::thread; use std::sync::atomic::{atomicbool, ordering}; struct theatre { rows: vec<row> } struct row { seats: vec<seat> } struct seat { number: i32, booked: atomicbool, } impl seat { fn new(number: i32) -> seat { seat { number: number, booked: atomicbool::new(false) } } fn book(&self) { self.booked.store(true, ordering::release); println!("booked seat: {:?}", self.number); } } impl theatre { fn get_booking<'t>(&'t self, number: i32) -> option<booking<'t>> { row in self.rows.iter() { seat in row.seats.iter() { if seat.number == number && seat.booked.load(ordering::acquire) == false { return some(booking { seats: vec![ seat ] }) } } } none } } struct booking<'t> { seats: vec<&'t seat> } impl<'t> booking<'t> { fn describe(&self) { let seats: vec<_> = self.seats.iter().map(|s| s.number).collect(); println!("you want book seats: {:?}", seats); } fn run(&self) { seat in self.seats.iter() { seat.book(); } } } fn main() { // build theatre (with 1 seat... small theatre) let theatre = theatre { rows: vec![ row { seats: vec![ seat::new(7) ] } ] }; let wrapper = arc::new(rwlock::new(theatre)); // try book seat in thread let thread = thread::spawn(move || { let desired_seat_number = 7; let t = wrapper.read().unwrap(); let booking = t.get_booking(desired_seat_number).expect("no such seat!"); booking.describe(); booking.run(); }); thread.join().unwrap(); }
there 2 important changes:
- the
booked
field changedbool
atomicbool
. atomic types providestore
method available on immutable references. therefore, can makeseat::book()
takeself
immutable reference. if have more complex type not covered atomic types, should instead usemutex
orrwlock
. - i removed
&mut theatre
parameter onbooking::run()
. if not acceptable, please leave comment explain why need reference.
as found, cannot have both read lock , write lock active @ same time on rwlock
. however, booking
cannot live longer read lock on theatre
, because contains references inside theatre
. once release read lock, cannot guarantee references obtained remain valid when acquired lock later on. if that's problem, consider using arc
instead of simple borrowed pointers (&
).
Comments
Post a Comment