首页 文章

如何在没有借用检查器问题的情况下从函数返回新数据作为参考?

提问于
浏览
2

我正在编写一个函数,它接受一个整数的引用,并返回该整数的向量乘以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);
    println!("{:?}", q);
}

借用检查器变得疯狂,因为我定义了一个新变量,它在堆栈上分配,并在函数末尾超出范围 .

我该怎么办?当然我可以知道 BoxCopy 类型的解决方法,但我对一个惯用的Rust解决方案很感兴趣 .

我意识到我可以回复 Vec<i64> ,但我认为会遇到同样的问题?主要试图为一般问题想出一个"emblematic"问题:)

1 回答

  • 4

    编辑:我只是意识到你写了"I'm aware there's Box, Copy etc type workaround but I'm mostly interested in an idiomatic rust solution",但我已经输入了整个答案 . :P下面的解决方案是惯用的Rust,这就是内存的工作原理!不要阻止你,这并不代表任何好事 . ;)


    每次返回引用时,该引用必须是函数的参数 . 换句话说,如果要返回对数据的引用,则必须在函数外部分配所有数据 . 你似乎明白这一点,我只是想确保它清楚 . :)

    根据您的用例,有许多可能的方法来解决此问题 .

    在这个特定的例子中,因为之后你不需要 x ,所以你可以将所有权赋予 foo ,而不必费心去参考:

    fn foo(x: i64) -> Vec<i64> {
        std::iter::repeat(x * 2).take(5).collect()
    }
    
    fn main() {
        let x = 5;
        println!("{:?}", foo(x));
    }
    

    但是让's say that you don'想要将所有权转移到 foo . 只要您不想改变基础值,您仍然可以返回引用向量:

    fn foo(x: &i64) -> Vec<&i64> {
        std::iter::repeat(x).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));
    }
    

    ...尽管如此,我只是想确保你知道 Box 是使用堆的一般方法 . 说实话,只需使用 Vec 意味着您的数据将被放置在堆上,因此这有效:

    fn foo(x: &i64) -> Vec<i64> {
        std::iter::repeat(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);
    }
    

    最后,如果函数必须将引用作为参数,并且必须改变引用的数据,并且必须复制引用本身并且必须返回这些复制的引用,那么您可以使用 Cell

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

    Cell 既高效又不令人惊讶,但请注意 Cell 仅适用于实现 Copy 特性的类型(所有原始数字类型都可以) . 如果你的类型没有实现 Copy 那么你仍然可以使用 RefCell 做同样的事情,但它会带来轻微的运行时开销,并且如果你得到_2858285错误,就会在运行时开启恐慌的可能性 .

相关问题