如何将引用升级为可变引用?
How do I upgrade a reference to a mutable reference?
我有一个围绕一个共享数据结构的程序,提出对数据的更改,然后在稍后阶段应用这些更改。这些提议的更改包含对核心对象的引用。
在 C++ 或其他语言中,我会简单地使引用成为非常量,然后在需要时改变它。但是 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 可以接受的东西,但不太清楚如何构建我的程序来适应它。那么,有什么指示吗? (双关语)
首先,这是代码:
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 provide a store
method that is available on immutable references. Therefore, we can make Seat::book()
take self
by immutable reference. If you have a more complex type that is not covered by the atomic types, you should instead use a Mutex
or a RwLock
.
- 我删除了
Booking::run()
上的 &mut Theatre
参数。如果这不可接受,请发表评论以解释您需要该参考的原因。
如您所见,您不能在 RwLock
上同时激活读锁和写锁。但是,Booking
不能比 Theatre
上的读锁存在得更长,因为它包含 Theatre
内部的引用。释放读锁后,您无法保证您获得的引用在您稍后获得另一个锁时仍然有效。如果这是一个问题,请考虑使用 Arc
而不是简单的借用指针 (&
)。
我有一个围绕一个共享数据结构的程序,提出对数据的更改,然后在稍后阶段应用这些更改。这些提议的更改包含对核心对象的引用。
在 C++ 或其他语言中,我会简单地使引用成为非常量,然后在需要时改变它。但是 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 可以接受的东西,但不太清楚如何构建我的程序来适应它。那么,有什么指示吗? (双关语)
首先,这是代码:
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 provide astore
method that is available on immutable references. Therefore, we can makeSeat::book()
takeself
by immutable reference. If you have a more complex type that is not covered by the atomic types, you should instead use aMutex
or aRwLock
.- 我删除了
Booking::run()
上的&mut Theatre
参数。如果这不可接受,请发表评论以解释您需要该参考的原因。
如您所见,您不能在 RwLock
上同时激活读锁和写锁。但是,Booking
不能比 Theatre
上的读锁存在得更长,因为它包含 Theatre
内部的引用。释放读锁后,您无法保证您获得的引用在您稍后获得另一个锁时仍然有效。如果这是一个问题,请考虑使用 Arc
而不是简单的借用指针 (&
)。