如何将引用升级为可变引用?

Ben*_*n S 5 reference rust

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

在 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,该get_booking方法搜索座位,Booking并返回 a 及其找到的座位的引用。

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 Code Online (Sandbox Code Playgroud)

但这就是我陷入困境的地方。该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();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,如果它有效的话,将使用它的主要方法。

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();
}
Run Code Online (Sandbox Code Playgroud)

令人烦恼的是,我确切地知道为什么我当前的代码不起作用 - 我只是不明白 Rust 希望我的程序如何格式化。有一些事情我不想做:

  • 最简单的解决方案是Booking在其位置上保存索引,而不是引用:在本例中,使用rowseat usize字段。然而,虽然我的剧院使用 O(1) 向量,但我还想引用一棵大树中间的值,其中必须迭代才能找到该值会更加昂贵。这也意味着您无法在describe不经过整个剧院的情况下获得座位号(在函数中)。
  • 也可以通过保留Booking对座位的可变引用来解决这个问题,然后我可以像平常一样对其进行变异。但是,这意味着我一次只能提出一项更改建议:例如,我无法拥有一份预订列表并一次应用它们,或者有两个预订但只应用一个。

我觉得我非常接近 Rust 会接受的东西,但不太知道如何构建我的程序来适应它。那么,有什么指示吗?(双关语)

Fra*_*gné 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();
}
Run Code Online (Sandbox Code Playgroud)

在婴儿围栏上查看

有两个重要的变化:

  1. booked字段从 更改boolAtomicBool。原子类型提供了store可用于不可变引用的方法。因此,我们可以通过不可变引用来Seat::book()获取。self如果您有原子类型未涵盖的更复杂的类型,则应该使用 aMutex或 a RwLock
  2. 我删除了&mut Theatre上的参数Booking::run()。如果这是不可接受的,请留下评论来解释为什么您需要该参考。

正如您所发现的,您不能在RwLock. 但是,a 的Booking生存时间不能长于 上的读锁Theatre,因为它包含 中的引用Theatre。一旦释放读锁,您就不能保证您获得的引用在您稍后获取另一个锁时仍然有效。如果这是一个问题,请考虑使用Arc而不是简单的借用指针 ( &)。