rust 有什么方法可以返回对在函数中创建的变量的引用吗?

slsn1g29  于 2023-01-30  发布在  其他
关注(0)|答案(5)|浏览(311)

我想写一个程序,将写一个文件在2个步骤。这是可能的,该文件可能不存在之前,该程序运行。文件名是固定的。
问题是OpenOptions.new().write()可能会失败。在这种情况下,我想调用一个自定义函数trycreate()。其思想是创建文件而不是打开它并返回一个句柄。由于文件名是固定的,trycreate()没有参数,我无法设置返回值的生存期。
如何解决此问题?

use std::io::Write;
use std::fs::OpenOptions;
use std::path::Path;

fn trycreate() -> &OpenOptions {
    let f = OpenOptions::new().write(true).open("foo.txt");
    let mut f = match f {
        Ok(file)  => file,
        Err(_)  => panic!("ERR"),
    };
    f
}

fn main() {
    {
        let f = OpenOptions::new().write(true).open(b"foo.txt");
        let mut f = match f {
            Ok(file)  => file,
            Err(_)  => trycreate("foo.txt"),
        };
        let buf = b"test1\n";
        let _ret = f.write(buf).unwrap();
    }
    println!("50%");
    {
        let f = OpenOptions::new().append(true).open("foo.txt");
        let mut f = match f {
            Ok(file)  => file,
            Err(_)  => panic!("append"),
        };
        let buf = b"test2\n";
        let _ret = f.write(buf).unwrap();
    }
    println!("Ok");
}
nwo49xxi

nwo49xxi1#

∮你问的问题∮
TL;DR:不可以,不能返回对函数所拥有的变量的引用。如果您创建了该变量或将该变量的所有权作为函数参数,则此情况适用。

溶液

不要试图返回引用,而是返回一个拥有的对象。String代替&strVec<T>代替&[T]T代替&T,等等。
如果您通过参数获取变量的所有权,请尝试获取(可变)引用,然后返回具有相同生存期的引用。
在极少数情况下,你可以使用不安全代码来返回拥有的值 * 和 * 一个对它的引用。这有很多微妙的要求,你必须遵守,以确保你不会导致未定义的行为或内存不安全。
另见:

更深的答案

fjh is absolutely correct,但我想更深入地进行评论,并触及您的代码中的一些其他错误。
让我们从一个返回引用的小例子开始,看看错误:

fn try_create<'a>() -> &'a String {
    &String::new()
}

** rust 2015**

error[E0597]: borrowed value does not live long enough
 --> src/lib.rs:2:6
  |
2 |     &String::new()
  |      ^^^^^^^^^^^^^ temporary value does not live long enough
3 | }
  | - temporary value only lives until here
  |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 1:15...
 --> src/lib.rs:1:15
  |
1 | fn try_create<'a>() -> &'a String {
  |               ^^

** rust 2018**

error[E0515]: cannot return reference to temporary value
 --> src/lib.rs:2:5
  |
2 |     &String::new()
  |     ^-------------
  |     ||
  |     |temporary value created here
  |     returns a reference to data owned by the current function

有没有办法从一个没有参数的函数返回一个引用?
从技术上说“是”,但对于你想要的,“不是”。
一个reference指向一个已经存在的内存块,在一个没有参数的函数中,唯一可以被引用的是全局常量(生存期为&'static)和局部变量,我暂时忽略全局变量。
在像C或C++这样的语言中,你实际上可以引用一个局部变量并返回它。然而,一旦函数返回,不能保证你所引用的内存仍然是你所认为的那样。它可能会保持你所期望的一段时间。但最终内存会被其他东西重用。一旦代码查看内存并试图将用户名解释为用户银行帐户中的剩余金额,问题就会出现!
这就是Rust的生命周期所防止的--不允许使用超过被引用值在当前内存位置有效时间的引用。
另见:

你真正的问题
查看OpenOptions::open的文档:

fn open<P: AsRef<Path>>(&self, path: P) -> Result<File>

它返回一个Result<File>,所以我不知道你希望如何返回一个OpenOptions或者一个对OpenOptions的引用。如果你把函数重写为:

fn trycreate() -> File {
    OpenOptions::new()
        .write(true)
        .open("foo.txt")
        .expect("Couldn't open")
}

这里使用Result::expect来产生一个有用的错误消息,当然,在程序内部产生恐慌并不是非常有用,所以建议将错误传播出去:

fn trycreate() -> io::Result<File> {
    OpenOptions::new().write(true).open("foo.txt")
}

OptionResult有很多很好的方法来处理连锁错误逻辑。

let f = OpenOptions::new().write(true).open("foo.txt");
let mut f = f.or_else(|_| trycreate()).expect("failed at creating");

我还将返回main中的Result,包括fjh的建议:

use std::{
    fs::OpenOptions,
    io::{self, Write},
};

fn main() -> io::Result<()> {
    let mut f = OpenOptions::new()
        .create(true)
        .write(true)
        .append(true)
        .open("foo.txt")?;

    f.write_all(b"test1\n")?;
    f.write_all(b"test2\n")?;

    Ok(())
}
shyt4zoc

shyt4zoc2#

有没有办法从一个没有参数的函数返回一个引用?
否(除了对静态值的引用,但这些在这里没有帮助)。
但是,您可能需要查看OpenOptions::create。如果将main中的第一行更改为

let  f = OpenOptions::new().write(true).create(true).open(b"foo.txt");

这个文件将被创建,如果它还不存在,这将解决你原来的问题。

li9yvcax

li9yvcax3#

不能返回指向局部变量的引用。有两种选择:返回值或使用静态变量。
原因如下:
引用是指向内存位置的指针。一旦函数被执行,局部变量就从执行堆栈中弹出,资源也被释放。在那之后,任何对局部变量的引用都将指向一些无用的数据。由于它被释放,它不再由我们的程序拥有,操作系统可能已经把它给了另一个进程,我们的数据可能已经被覆盖。
对于下面的示例,x在函数运行时创建,并在函数完成执行时删除。它是函数的局部变量,位于此特定函数的堆栈上。函数的堆栈保存局部变量。
run从执行栈中弹出时,任何对x的引用&x都将指向一些垃圾数据。这就是人们所说的悬空指针。Rust编译器不允许使用悬空指针,因为它不安全。

fn run() -> &u32 {
    let x: u32 = 42;

    return &x;
} // x is dropped here

fn main() {
    let x = run();
}

这就是为什么我们不能返回一个局部变量的引用,我们有两种选择:返回值或使用静态变量。
返回值是这里最好的选择。通过返回值,您将把计算的结果传递给调用者,用Rust的话来说,x将归调用者所有。在我们的例子中,它是main。所以,没有问题。
由于静态变量的生命周期与进程的运行时间一样长,所以它的引用在函数内外都指向同一个内存位置,这也没有问题。
注:@navigaid建议使用box,但这没有意义,因为您是通过装箱然后返回的方式将现成可用的数据移动到堆中。它不能解决问题。你仍然把局部变量返回给调用者,但是在访问它的时候使用了一个指针。2由于取消引用而增加了不必要的间接访问,从而导致额外的开销。基本上,您将使用&是为了使用它,仅此而已。

jc3wubiy

jc3wubiy4#

这是对snnsnn's answer的详细说明,它简要地解释了问题,但不太具体。
Rust不允许返回对函数中创建的变量的引用。有解决方法吗?有,只需将该变量放入Box中,然后返回它。示例:

fn run() -> Box<u32> {
    let x: u32 = 42;
    return Box::new(x);
} 

fn main() {
    println!("{}", run());
}

code in rust playground
根据经验,为了避免在Rust中出现类似的问题,返回一个拥有的对象(Box、Vec、String ...),而不是引用一个变量:

  • Box<T>而不是&T
  • Vec<T>而不是&[T]
  • String而不是&str

对于其他类型,请参考The Periodic Table of Rust Types以确定要使用哪个拥有的对象。
当然,在本例中,您可以简单地返回值(T代替&TBox<T>

fn run() -> u32 {
    let x: u32 = 42;
    return x;
}
f4t66c6m

f4t66c6m5#

    • 是的!**但是你必须找到一种延长生存期的方法。一种方法是为函数提供对伪值/默认值的可变引用(& mut T),然后填充/替换函数中的值,然后返回对该值的引用(& T)。这样你就可以指定生存期,以便返回的引用获得函数外部值的生存期。

示例:

//&mut T -> &T
fn example2<'a>(life: &'a mut Vec<i32>) -> &'a Vec<i32> {
    *life = vec![1, 2, 3, 4];
    life
}

fn test_example2() {
    //Could also use Vec::new()
    let mut life = Vec::default();
    let res = example2(&mut life);
    println!("{:?}", res)
}

fn test2_example2() {
    let life = &mut Vec::default();
    let res = example2(life);
    println!("{:?}", res)
}

//shows real use case
fn get_check_test_slices2<'a>(
    lifetime: &'a mut Vec<usize>,
    limit: usize,
) -> impl Iterator<Item = (&'a [usize], &'a [usize])> + 'a {
    // create a list of primes using a simple primes sieve
    *lifetime = primes1_iter_bitvec(limit).collect::<Vec<_>>();
    // iterate through pairs of sub slices without copying the primes vec
    // slices will be used to check that a complicated sieve is correct
    all_test_check_slices(lifetime)
}

编辑:这和只给函数& mut T有什么不同?(Chayim Friedman问的(见下面的旧解决方案)):基本上是一样的......我之前输给了借位检查器,这就是为什么我没有直接使用& mut T。但经过重新的战斗,我终于设法直接使用& mut T。谢谢你的深刻问题。
旧解决方案:我的解决方案是在调用函数之前创建一个默认值,函数稍后会替换/填充该值并返回一个引用。

/// Used to return references to values created in a function.
/// fn example<'a>(lt:& 'a mut LifeExtender<Vec<i32>>) -> &'a Vec<i32> {
///     lt.set(vec![1,2,3,4]);
///     lt.get()
/// }
pub struct LifeExtender<T> {
    value: T,
}

impl<T> Default for LifeExtender<T>
where
    T: Default,
{
    /// using T default.
    fn default() -> Self {
        Self {
            value: T::default(),
        }
    }
}

impl<T> LifeExtender<T> {
    /// If T doesn't have default.
    pub fn new(value: T) -> Self {
        Self { value }
    }
    /// set value to be returned by reference
    pub fn set(&mut self, new_value: T) {
        self.value = new_value;
    }
    /// Get a reference with lifetime self.
    pub fn get<'a>(&'a self) -> &'a T {
        &self.value
    }
    /// Get a mut reference with lifetime self.
    pub fn get_mut<'a>(&'a mut self) -> &'a mut T {
        &mut self.value
    }
}

fn example<'a>(life: &'a mut LifeExtender<Vec<i32>>) -> &'a Vec<i32> {
    let local_value = vec![1, 2, 3, 4];
    life.set(local_value);
    life.get()
}

//prints: [1,2,3,4]
pub fn test_example() {
    let mut life = LifeExtender::default();
    let res = example(&mut life);
    println!("{:?}", res);
}

//Real example code snippet, where I used this solution: 
fn get_check_slices2<'a>(
    lifetime: &'a mut LifeExtender<Vec<usize>>,
    limit: usize,
) -> impl Iterator<Item = (&'a [usize], &'a [usize])> + 'a {
    lifetime.set(primes1_iter_bitvec(limit).collect::<Vec<_>>());
    all_test_check_slices(lifetime.get())
}

相关问题