Rust 如何从函数返回新数据作为引用,而不出现借用检查器问题?
我正在编写一个函数,它引用一个整数并返回该整数乘以2,5次的向量。我想这看起来像:Rust 如何从函数返回新数据作为引用,而不出现借用检查器问题?,rust,Rust,我正在编写一个函数,它引用一个整数并返回该整数乘以2,5次的向量。我想这看起来像: fn foo(x: &i64) -> Vec<&i64> { let mut v = vec![]; for i in 0..5 { let q = x * 2; v.push(&q); } v } fn main() { let x = 5; let q = foo(&x);
fn foo(x: &i64) -> Vec<&i64> {
let mut v = vec![];
for i in 0..5 {
let q = x * 2;
v.push(&q);
}
v
}
fn main() {
let x = 5;
let q = foo(&x);
println!("{:?}", q);
}
fn foo(x:&i64)->Vec{
设mut v=vec![];
因为我在0..5{
设q=x*2;
v、 推送(&q);
}
v
}
fn main(){
设x=5;
设q=foo(&x);
println!(“{:?}”,q);
}
借用检查器发疯了,因为我定义了一个新变量,它被分配到堆栈上,并且在函数末尾超出了范围
我该怎么办?当然,如果不编写创建新数据的函数,我就无法度过一生!我知道有Box
和Copy
类型的变通方法,但我对一个惯用的解决方案感兴趣
我意识到我可以返回一个
Vec
,但我认为这会遇到同样的问题?主要是想为一般问题提出一个“象征性”的问题:)编辑:我刚刚意识到你写了“我知道有框、复制等类型的解决方法,但我主要对惯用的生锈解决方案感兴趣”,但我已经键入了全部答案:P和下面的解决方案都是习惯用法,这就是内存的工作原理!不要试图返回指针到堆栈分配的数据在C或C++中,因为即使编译器没有阻止你,但这并不意味着它会有什么好的结果。p>
无论何时返回引用,该引用都必须是函数的参数。换句话说,如果您返回对数据的引用,那么所有这些数据都必须在函数外部分配。你似乎明白这一点,我只是想确定这是清楚的。:) 根据您的用例,有许多潜在的方法来解决这个问题 在这个特定的示例中,因为您以后不需要
x
做任何事情,所以您可以将所有权授予foo
,而不必担心引用:
fn foo(x: i64) -> Vec<i64> {
std::iter::repeat(x * 2).take(5).collect()
}
fn main() {
let x = 5;
println!("{:?}", foo(x));
}
…同样,只要您不想向基础值发出新指针,就可以对其进行变异:
fn foo(x: &mut i64) -> &mut i64 {
*x *= 2;
x
}
fn main() {
let mut x = 5;
println!("{:?}", foo(&mut x));
}
…但当然,你想两者兼而有之。因此,如果您正在分配内存,并且希望返回内存,那么您需要在堆栈之外的其他位置执行此操作。您可以做的一件事就是使用Box
将其填充到堆中:
// Just for illustration, see the next example for a better approach
fn foo(x: &i64) -> Vec<Box<i64>> {
std::iter::repeat(Box::new(x * 2)).take(5).collect()
}
fn main() {
let x = 5;
println!("{:?}", foo(&x));
}
上面可能是这里最惯用的例子,尽管您的用例可能需要一些不同的东西
或者,您可以从C的playbook中提取一个技巧,在foo
之外预先分配内存,然后传入对它的引用:
fn foo(x: &i64, v: &mut [i64; 5]) {
for i in v {
*i = x * 2;
}
}
fn main() {
let x = 5;
let mut v = [0; 5]; // fixed-size array on the stack
foo(&x, &mut v);
println!("{:?}", v);
}
最后,如果函数必须将引用作为其参数,并且必须对引用数据进行变异,并且必须复制引用本身,并且必须返回这些复制的引用,则可以使用单元格
:
use std::cell::Cell;
fn foo(x: &Cell<i64>) -> Vec<&Cell<i64>> {
x.set(x.get() * 2);
std::iter::repeat(x).take(5).collect()
}
fn main() {
let x = Cell::new(5);
println!("{:?}", foo(&x));
}
使用std::cell::cell;
fn foo(x:&Cell)->Vec{
x、 set(x.get()*2);
std::iter::repeat(x).take(5).collect()
}
fn main(){
设x=Cell::new(5);
println!(“{:?}”,foo(&x));
}
Cell
既高效又不足为奇,不过请注意,Cell
仅适用于实现Copy
特征的类型(所有基本数字类型都是这样)。如果您的类型没有实现Copy
,那么您仍然可以使用RefCell
执行同样的操作,但这会带来轻微的运行时开销,并且如果您的“借用”错误,在运行时可能会出现恐慌。但我认为这也会遇到同样的问题-您尝试过吗?为什么你认为它会有同样的问题?@Shepmaster我认为如果我在堆栈上的新函数中分配i64,它会生气。但它似乎是有效的。我猜这是因为我把所有权还给了打电话的人,所以借钱人不会感到不安,也就是说,没有借钱?这种解决方案是否比使用包含堆的分配结构(如Box或Cell)更为惯用?正确的是,不涉及借用:在堆栈上分配i64
s,然后将它们的所有权转移给Vec
,然后将Vec
的所有权转移回调用方。与使用包含堆分配的结构相比,更惯用的是-aVec
是“包含堆分配的结构”。我想说,在这种情况下使用Vec
比使用Box
或Cell
更为惯用。在这个特定的示例中,您还可以返回一个数组[i64;5]
,因为您知道将返回多少个数字。非常感谢。使它更清晰了一百万倍,并且很高兴知道我没有疯!我想这似乎是为了避免像盒子这样的东西,但如果它们没有开销,我想谁会在乎呢?唯一的问题是,返回带有fn内制造的i64的Vec是否会被视为“更惯用”?为了避免堆分配东西?@William,re:Vec
是最常用的方法,答案是肯定的,我更新了上面的内容以反映这一点。我使用Box
的示例只是为了确保有很多方法可以使用堆。如果您有一个想要“返回新数据”的函数,并且还没有返回Vec
,那么您将使用Box
来代替它。@William,我也不会说Box
没有开销,因为堆分配比堆栈分配有更多的开销。如果你真的需要最大的性能(但是提防过早的优化等),你应该考虑C风格的方法,你把它传递给在更早的堆栈帧中分配的内存。为了说明这一点,我甚至会更新C风格的示例,使用堆栈分配的数组,而不是Vec
。
fn foo(x: &i64, v: &mut [i64; 5]) {
for i in v {
*i = x * 2;
}
}
fn main() {
let x = 5;
let mut v = [0; 5]; // fixed-size array on the stack
foo(&x, &mut v);
println!("{:?}", v);
}
use std::cell::Cell;
fn foo(x: &Cell<i64>) -> Vec<&Cell<i64>> {
x.set(x.get() * 2);
std::iter::repeat(x).take(5).collect()
}
fn main() {
let x = Cell::new(5);
println!("{:?}", foo(&x));
}