如何在Rust中可变引用多个对象中的一个?以轨道切换写入场景为例
问题描述
需要实现一个Train结构体,能随时在两条轨道间切换,并在当前轨道的当前位置写入u8类型数据。核心要求是write方法必须高效,不能在选择轨道上消耗额外时间。
初始实现思路如下,但无法编译,原因是对track_a和track_b进行了多次可变借用:
struct Train<'a> { track_a: &'a mut [u8], track_b: &'a mut [u8], current_track: &'a mut [u8], // 指向track_a或track_b idx: usize, } impl<'a> Train<'a> { pub fn new(track_a: &'a mut [u8], track_b: &'a mut [u8]) -> Self { Self { track_a, track_b, idx: 0, current_track: track_a, } } pub fn toggle_track(&mut self) { if self.current_track == self.track_a { self.current_track = self.track_b; } else { self.current_track = self.track_a; } } pub fn write(&mut self, byte: u8) { // 必须高效,不能在这里浪费时间选择轨道 self.current_track[self.idx] = byte; self.idx += 1; } }
尝试过用RefCell包裹轨道,但发现不合理——即使是不可变的Ref也能获取底层字节的可变访问权限。
请问有没有办法在Rust中实现这个数据结构?是否必须使用不安全Rust(unsafe Rust)?
解决方案
方案一:用枚举封装当前轨道(无需unsafe)
可以不用unsafe,通过枚举跟踪当前激活的轨道,避免重复借用问题。这种方式的write方法依然高效,因为枚举匹配是零成本抽象,编译后会直接跳转对应轨道的写入逻辑:
struct Train<'a> { track_a: &'a mut [u8], track_b: &'a mut [u8], idx: usize, current: CurrentTrack, } enum CurrentTrack { A, B, } impl<'a> Train<'a> { pub fn new(track_a: &'a mut [u8], track_b: &'a mut [u8]) -> Self { Self { track_a, track_b, idx: 0, current: CurrentTrack::A, } } pub fn toggle_track(&mut self) { self.current = match self.current { CurrentTrack::A => CurrentTrack::B, CurrentTrack::B => CurrentTrack::A, }; } pub fn write(&mut self, byte: u8) { match self.current { CurrentTrack::A => self.track_a[self.idx] = byte, CurrentTrack::B => self.track_b[self.idx] = byte, } self.idx += 1; } }
Rust编译器会对枚举匹配做优化,最终生成的机器码和直接用指针访问几乎无差异,完全满足write方法的高效要求。
方案二:使用unsafe Rust(贴近初始思路)
如果坚持要保留「直接持有当前轨道指针」的设计,可以用unsafe绕过借用检查,但需要自行保证内存安全:
use std::ptr; struct Train<'a> { track_a: &'a mut [u8], track_b: &'a mut [u8], current_track: *mut [u8], idx: usize, } impl<'a> Train<'a> { pub fn new(track_a: &'a mut [u8], track_b: &'a mut [u8]) -> Self { Self { track_a, track_b, current_track: track_a as *mut [u8], idx: 0, } } pub fn toggle_track(&mut self) { self.current_track = if self.current_track == self.track_a as *mut [u8] { self.track_b as *mut [u8] } else { self.track_a as *mut [u8] }; } pub fn write(&mut self, byte: u8) { // 需确保current_track始终指向track_a或track_b,且idx不越界 unsafe { let track = &mut *self.current_track; track[self.idx] = byte; } self.idx += 1; } }
这种方式完全贴合初始设计,write方法直接通过指针访问当前轨道,无额外分支开销。但要注意:
- 必须保证
current_track永远只指向track_a或track_b,不能变成悬空指针 - 必须保证
idx不会超出当前轨道的长度,否则会触发内存越界
方案对比
- 方案一(枚举):完全安全,编译器自动保障内存安全,性能和unsafe版本几乎一致,优先推荐。
- 方案二(unsafe):更贴近初始设计,但需要手动维护内存安全,适合对底层指针操作有明确需求的场景。
内容的提问来源于stack exchange,提问作者mwlon
相关产品推荐
相关产品推荐

