gpt4 book ai didi

rust - 在循环中发生变异时,可变借用时间过长

转载 作者:行者123 更新时间:2023-12-03 11:26:50 25 4
gpt4 key购买 nike

我有一段代码需要存储 String 并访问对这些字符串的引用。我先是这样写的:

struct Pool {
strings : Vec<String>
}

impl Pool {
pub fn new() -> Self {
Self {
strings: vec![]
}
}

pub fn some_f(&mut self) -> Vec<&str> {
let mut v = vec![];

for i in 1..10 {
let string = format!("{}", i);
let string_ref = self.new_string(string);
v.push(string_ref);
}

v
}

fn new_string(&mut self, string : String) -> &str {
self.strings.push(string);
&self.strings.last().unwrap()[..]
}
}

这没有通过借用检查器:

error[E0499]: cannot borrow `*self` as mutable more than once at a time
--> src/main.rs:19:30
|
14 | pub fn some_f(&mut self) -> Vec<&str> {
| - let's call the lifetime of this reference `'1`
...
19 | let string_ref = self.new_string(string);
| ^^^^ mutable borrow starts here in previous iteration of loop
...
23 | v
| - returning this value requires that `*self` is borrowed for `'1`

显然,借用检查器不够聪明,无法意识到可变借用不会超出对 new_string 的调用。我尝试将改变结构的部分与检索引用分开,得到以下代码:

use std::vec::*;

struct Pool {
strings : Vec<String>
}

impl Pool {
pub fn new() -> Self {
Self {
strings: vec![]
}
}

pub fn some_f(&mut self) -> Vec<&str> {
let mut v = vec![];

for i in 1..10 {
let string = format!("{}", i);
self.new_string(string);
}
for i in 1..10 {
let string = &self.strings[i - 1];
v.push(&string[..]);
}

v
}

fn new_string(&mut self, string : String) {
self.strings.push(string);
}
}

这在语义上是等效的(希望如此)并且可以编译。然而,将两个 for 循环合并为一个:

for i in 1..10 {
let string = format!("{}", i);
self.new_string(string);
let string = &self.strings[i - 1];
v.push(&string[..]);
}

给出类似的借用错误:

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
--> src/main.rs:19:13
|
14 | pub fn some_f(&mut self) -> Vec<&str> {
| - let's call the lifetime of this reference `'1`
...
19 | self.new_string(string);
| ^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
20 | let string = &self.strings[i - 1];
| ------------ immutable borrow occurs here
...
24 | v
| - returning this value requires that `self.strings` is borrowed for `'1`

我有几个问题:

  1. 在这种情况下,为什么借用检查器如此严格,以至于在整个循环期间扩展可变借用?是否不可能/很难分析传递给 new_string&mut 不会泄漏到该函数调用之外?

  2. 是否可以通过自定义生命周期解决此问题,以便我可以返回到既可以变异又可以返回引用的原始助手?

  3. 是否有一种不同的、更符合 Rust 习惯的方式,它不会扰乱借用检查器,我可以在其中实现我想要的,即拥有一个可以变异并返回对自身的引用的结构?

我找到了 this question ,但我不明白答案(这是对 #2 的否定回答吗?不知道),而且大多数其他问题都存在显式生命周期参数的问题。我的代码仅使用推断的生命周期。

最佳答案

在这种情况下,借用检查器不允许这样做是正确的:

    self.new_string(string);
let string = &self.strings[i - 1];
v.push(&string[..]);

self.new_string 可能会导致您推送到 v 的所有先前引用变得无效,因为它可能需要为 strings 分配内存> 并移动其内容。借用检查器会捕捉到这一点,因为您推送到 v 的引用需要一个生命周期来匹配 v,所以 &self.strings(因此 &self) 必须为整个方法借用,这可以防止你的可变借用。

如果您使用两个循环,则在您调用 new_string 时没有事件的共享借用。

你可以看到问题不是被扩展的可变借用,在这个(完全无用的)版本的循环中,编译:

for i in 1..10 {
let string = format!("{}", i);
self.new_string(string);
let mut v2 = vec![];
let string = &self.strings[i - 1];
v2.push(&string[..]);
}

至于更惯用的方式,Vec 类可以自由地使可变操作中的引用无效,因此您不能在 safe rust 中做您想做的事。即使编译器允许,您也不想使用 C++ 向量来执行此操作,除非您预先分配向量并手动确保永远不会推送比最初分配的元素更多的元素。显然 Rust 不希望您手动验证程序的内存安全性;预分配的大小在类型系统中不可见,借用检查器也无法检查,因此这种方法是不可能的。

即使使用像 [String; 这样的固定大小的容器,你也无法解决这个问题。 10]。在那种情况下可能没有分配,但实际上使这种安全的是你永远不会更新你已经从中引用的索引的事实。但是 Rust 没有从容器中部分借用的概念,所以没有办法告诉它“有一个共享借用到索引 n,所以我可以从索引 n + 1 进行可变借用”。

如果您出于性能原因确实需要一个循环,则需要预分配并使用不安全的 block ,例如:

struct Pool {
strings: Vec<String>,
}

const SIZE: usize = 10;

impl Pool {
pub fn new() -> Self {
Self {
strings: Vec::with_capacity(SIZE),
}
}

pub fn some_f(&mut self) -> Vec<&str> {
let mut v: Vec<&str> = vec![];

// We've allocated 10 elements, but the loop uses 9, it's OK as long as it's not the other way around!
for i in 1..SIZE {
let string = format!("{}", i);
self.strings.push(string);
let raw = &self.strings as *const Vec<String>;
unsafe {
let last = (*raw).last().unwrap();
v.push(last);
}
}

v
}
}

一个可能的替代方案是使用 Rc,但如果您想要单个循环的原因是性能,那么使用堆 + 引用计数的运行时成本可能是一个糟糕的权衡。无论如何,这里是代码:

use std::rc::Rc;

struct Pool {
strings: Vec<Rc<String>>,
}

impl Pool {
pub fn new() -> Self {
Self { strings: vec![] }
}

pub fn some_f(&mut self) -> Vec<Rc<String>> {
let mut v = vec![];

for i in 1..10 {
let string = format!("{}", i);
let rc = Rc::new(string);
let result = rc.clone();
self.strings.push(rc);
v.push(result);
}

v
}
}

关于rust - 在循环中发生变异时,可变借用时间过长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64627891/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com