【问题标题】:How do I upgrade a reference to a mutable reference?如何将引用升级为可变引用?
【发布时间】:2015-07-01 23:34:41
【问题描述】:

我有一个程序,它围绕一个共享数据结构,提出对数据的更改,然后在稍后阶段应用这些更改。这些提议的更改包含对核心对象的引用。

在 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 为其座位保留索引,而不是引用:在这种情况下,使用rowseat usize 字段。但是,尽管我的剧院使用 O(1) 向量,但我还想在大树中间引用一个值,而必须迭代才能找到该值会更昂贵。这也意味着您无法在不经过整个剧院的情况下获得座位号(在 describe 函数中)。
  • 也可以通过让Booking 持有对座位的可变引用来解决,然后我可以像往常一样对其进行变异。但是,这意味着我一次只能有一个提议的更改:例如,我不能有一个预订列表并同时应用它们,或者有两个预订并且只应用一个。

我觉得我非常接近拥有 Rust 可以接受的东西,但不太清楚如何构建我的程序来适应它。那么,任何指针? (双关语)

【问题讨论】:

    标签: reference rust


    【解决方案1】:

    首先,代码如下:

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

    View on playpen

    有两个重要的变化:

    1. booked 字段已从 bool 更改为 AtomicBoolatomic types 提供了一个在不可变引用上可用的 store 方法。因此,我们可以通过不可变引用使Seat::book()self。如果您有原子类型未涵盖的更复杂类型,则应改用MutexRwLock
    2. 我删除了Booking::run() 上的&amp;mut Theatre 参数。如果这不可接受,请发表评论以说明您需要该参考的原因。

    如您所见,您不能同时在RwLock 上同时激活读锁和写锁。但是,Booking 的寿命不能超过Theatre 上的读锁,因为它包含Theatre 内的引用。一旦你释放了读锁,你不能保证你获得的引用在你以后获得另一个锁时仍然有效。如果这是个问题,请考虑使用 Arc 而不是简单的借用指针 (&amp;)。

    【讨论】:

    • 非常感谢您!这是否意味着在我的数据结构中,我需要将我想要更改的所有值包装在 Mutex 或 RwLock 后面?
    • 是的。此外,如果您使用Arcs,您还必须将它们包装在Arc 中。
    • @BenS: MutexRwLock 如果你使用多线程,否则 CellRefCell 就足够了。
    猜你喜欢
    • 2023-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多