有啥方法可以返回对函数中创建的变量的引用?

Posted

技术标签:

【中文标题】有啥方法可以返回对函数中创建的变量的引用?【英文标题】:Is there any way to return a reference to a variable created in a function?有什么方法可以返回对函数中创建的变量的引用? 【发布时间】:2015-12-17 10:11:15 【问题描述】:

我想编写一个分两步写入文件的程序。 在程序运行之前,该文件可能不存在。文件名是固定的。

问题是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");

【问题讨论】:

打开这个页面,Ctrl-F,“牛”,没有结果??虽然您不能返回对函数中创建的变量的引用,但您可以使用std::borrow::Cow 来概括拥有的数据和无主的引用——这是一个Deref,让您可以选择给定实例是拥有还是借用其数据。我发现它是在返回拥有和非拥有数据之间关闭的最可靠方法。 【参考方案1】:

你问的问题

TL;DR:不,您不能返回对函数拥有的变量的引用。这适用于您创建变量或将变量的所有权作为函数参数的情况。

解决方案

与其尝试返回引用,不如返回一个拥有的对象。 String 代替&strVec<T> 代替&[T]T 代替&T,等等

如果您通过参数获取变量的所有权,请尝试获取(可变)引用,然后返回相同生命周期的引用。

在极少数情况下,您可以使用不安全代码返回拥有的值对其的引用。这有一些您必须遵守的微妙要求,以确保您不会导致未定义的行为或内存不安全。

另见:

Proper way to return a new string in Rust Return local String as a slice (&str) Why can't I store a value and a reference to that value in the same struct?

更深层次的回答

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

有什么方法可以从不带参数的函数返回引用?

技术上是“是”,但对于你想要的,“否”。

引用指向现有内存。在没有参数的函数中,唯一可以引用的是全局常量(具有生命周期&amp;'static)和局部变量。我暂时忽略全局变量。

在像 C 或 C++ 这样的语言中,您实际上可以获取对局部变量的引用并返回它。但是,一旦函数返回,无法保证您引用的内存仍然是您认为的那样。它可能会在一段时间内保持您的预期,但最终内存将被重新用于其他用途。一旦您的代码查看内存并尝试将用户名解释为用户银行帐户中剩余的金额,就会出现问题!

这就是 Rust 的生命周期所阻止的——你不能使用超过被引用值在其当前内存位置有效的时间。

另见:

Is it possible to return either a borrowed or owned type in Rust? Why can I return a reference to a local literal but not a variable?

你的实际问题

查看OpenOptions::open的文档:

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

它返回一个Result&lt;File&gt;,所以我不知道您希望如何返回一个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 有很多很好的方法来处理链式错误逻辑。在这里,您可以使用or_else

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(())

【讨论】:

注意:在 C++ 中,返回对堆栈局部变量的引用是未定义行为;如果它看起来有效,那你就是不走运。在常见情况下,编译器应该检测到问题并发出警告。 @MatthieuM。只是一个警告......多么不安全 :-) 虽然它表明我已经有几年没有为日常工作编写 C 语言了,因为我从来没有看到任何这些警告。很高兴看到所有营地的进展! @Shepmaster:你一定是在使用旧的编译器;我仍然坚持使用 gcc 4.3.2 并且我拥有它!但是,是的,只是一个警告。大多数 C/C++ 编译器都采取保守的方法:错误是由标准规定的,其余的都是通过警告完成的(或多或少的准确性......) @D3181 没有没有方法调用Write。您正在寻找write,并且您需要在范围内具有该特征。查看What's the de-facto way of reading and writing files in Rust 1.x? 的 Rust 1.0 /“写入文件”部分。 @JohnDoe 是否包含在 the question linked in the answer 中?【参考方案2】:

有什么方法可以从不带参数的函数返回引用?

否(除了对静态值的引用,但这些在这里没有帮助)。

不过,您可能想查看OpenOptions::create。如果您将main 中的第一行更改为

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

如果文件尚不存在,将创建该文件,这应该可以解决您原来的问题。

【讨论】:

【参考方案3】:

这是对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&lt;T&gt; 而不是 &amp;T Vec&lt;T&gt; 而不是 &amp;[T] String 而不是 &amp;str

对于其他类型,请参阅The Periodic Table of Rust Types 以确定使用哪个拥有的对象。

当然,在本例中,您可以简单地返回值(T 而不是 &amp;TBox&lt;T&gt;

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

【讨论】:

这个答案是错误的和误导性的。你为什么要把一个现成的数据装箱然后返回它。它增加了不必要的间接性,从而增加了成本。 @snnsnn u32 变量仅用于演示目的。我已经在答案末尾指出了这一点。 即使你对一个适合装箱的变量进行装箱,该示例仍然是错误的,因为引用是用于将外部范围的引用传递到函数中,换句话说,用于借用外部变量,避免进出的混乱。你的例子完全颠倒了这个逻辑。它会让新手感到困惑,并且没有真正的用例。【参考方案4】:

引用是指向内存位置的指针。一旦函数被执行,局部变量就会从执行堆栈中弹出并释放资源。任何对局部变量的引用都将指向无用的数据。

对于以下示例,x 在函数运行时创建,并在函数执行完成时删除。它是函数的本地变量,并且存在于它的堆栈中。

run 从执行堆栈中弹出时,引用&amp;x 将指向一些垃圾数据。基本上它是一个悬空指针。 Rust 编译器不允许这样做,因为它不安全。

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

    return &x;
 // x is dropped here

fn main() 
    let x = run();

因此,您不能返回指向函数局部变量的引用。您有两种选择,要么返回值,要么使用静态变量。返回值是这里的最佳选择。通过返回值,您会将计算结果传递给外部函数,用 Rust 的术语来说,x 将归外部函数所有。在我们的例子中是main。所以,没问题。

由于静态变量只要进程运行就存在,它的引用将指向函数内部和外部的相同内存位置。那里也没有问题。

注意:@navigaid 建议使用盒子,但它没有意义,因为您通过装箱然后返回它来将现成的数据移动到堆中。它没有解决问题,您仍然将局部变量返回给调用者,但在访问它时使用指针。由于取消引用,它增加了不必要的间接性,因此会产生额外的成本。基本上,您将使用&amp; 来使用它,仅此而已。

【讨论】:

return 在这样的块末尾是不习惯的。 第一个答案不必要地冗长,第二个答案不够详细。选择返回作为重点。 尽管return 不习惯,但我发现这个答案有最清楚的解释。 这更有意义。编译器错误消息可能会更明显一些。我有一个案例,我传递了一个拥有的对象,但只返回对现在移动的对象的引用,该对象将在函数结束时被删除。我更习惯于阅读更常见的“x.foo()在这里借用xx在函数末尾被删除”

以上是关于有啥方法可以返回对函数中创建的变量的引用?的主要内容,如果未能解决你的问题,请参考以下文章

从 Oracle 12c 函数返回多个值

数据Array的常用方法

C ++:从函数、返回类型或引用中使用和返回字符数组?

请问函数的返回值类型那里,写“类名”和“类名&”有啥区别?

请问函数的返回值类型那里,写“类名”和“类名&”有啥区别?

swift中有啥更好:一个函数返回一个变量或只是一个getter变量[重复]