我有一个围绕一个共享数据结构的程序,建议对数据进行更改,然后在稍后阶段应用这些更改。这些提议的更改包含对核心对象的引用。
在C ++或其他语言中,我只是将引用设为非const,然后在需要时进行变异。但Rust并不适合这种方法。 (我今天早些时候在IRC询问过这个问题,但遗憾的是我仍然被卡住了。)
为了提供帮助,我做了一个用于在剧院预订门票的简单示例,其中剧院是数据结构,预订是建议的更改,run
方法将应用它们如果我能弄清楚如何让它工作!
首先,定义一些数据结构。剧院有很多排,每排都有很多座位:
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;
}
}
此处,get_booking
方法会搜索一个席位,并返回Booking
,其中包含对所找到席位的引用。
impl Theatre {
fn get_booking<'t>(&'t self, number: i32) -> Option<Booking<'t>> {
for row in self.rows.iter() {
for seat in row.seats.iter() {
if seat.number == number && seat.booked == false {
return Some(Booking { seats: vec![ seat ] })
}
}
}
None
}
}
但这是我被卡住的地方。 run
方法可以访问整个影院(来自其参数),并且知道要改变哪个席位(self
)。但由于self
不可变,即使包含它的剧院是,它也不能变异。
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 to book seats: {:?}", seats);
}
fn run(&self, _theatre: &mut Theatre) {
let mut seat = ??????;
seat.book();
}
}
最后,一个主要方法,如果它起作用,将使用它。
fn main() {
// Build a theatre (with only one seat... small theatre)
let theatre = Theatre { rows: vec![ Row { seats: vec![ Seat::new(7) ] } ] };
let wrapper = Arc::new(RwLock::new(theatre));
// Try to book a seat in another 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); // this is never actually reached because we still have the read lock
});
thread.join().unwrap();
}
令人讨厌的是,我确切地知道为什么我当前的代码不起作用 - 我只是无法弄清楚Rust是如何想要我的程序格式化的。我不想要做的事情有一些:
Booking
为其席位保留索引,而不是引用:在这种情况下,使用row
和seat
usize
字段。然而,虽然我的剧院使用O(1)向量,但我也想在大树的中间引用一个值,其中必须迭代以找到该值会更加昂贵。这也意味着您无法获得座位号(在describe
功能中),而无需通过整个剧院。Booking
持有对座位的可变引用来解决,然后我可以正常变异。但是,这意味着我一次只能提出一个建议的变更:例如,我不能有一份预订清单并立即应用它们,或者有两次预订而只能申请一次。我觉得我非常接近Rust会接受的东西,但不太知道如何构建我的程序以适应它。那么,任何指针? (双关语)
答案 0 :(得分:3)
首先,这是代码:
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>> {
for row in self.rows.iter() {
for 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 to book seats: {:?}", seats);
}
fn run(&self) {
for seat in self.seats.iter() {
seat.book();
}
}
}
fn main() {
// Build a theatre (with only one seat... small theatre)
let theatre = Theatre { rows: vec![ Row { seats: vec![ Seat::new(7) ] } ] };
let wrapper = Arc::new(RwLock::new(theatre));
// Try to book a seat in another 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();
}
有两个重要的变化:
booked
字段已从bool
更改为AtomicBool
。 atomic types提供了一个store
方法,该方法可用于不可变引用。因此,我们可以通过不可变引用使Seat::book()
取self
。如果您有一个原子类型未涵盖的更复杂的类型,则应使用Mutex
或RwLock
。&mut Theatre
上的Booking::run()
参数。如果这是不可接受的,请发表评论以解释为什么需要该参考。正如您所发现的那样,RwLock
上不能同时激活读锁定和写锁定。但是,Booking
的活动时间不能超过Theatre
上的读锁定,因为它包含Theatre
内的引用。一旦释放读锁定,就无法保证在以后获得另一个锁定时,您获得的引用将保持有效。如果这是一个问题,请考虑使用Arc
而不是简单的借用指针(&
)。