如何从函数中返回一个&Path?

20

我想要了解如何编写正确的Rust代码,但我觉得我可能高估了编译器理解我的对象生命周期的能力。这是我期望它可以工作的代码:

use std::path::Path;
use std::env;
use rusqlite::SqliteConnection;

struct SomeDatabase {
    conn: SqliteConnection,
}

impl SomeDatabase {
    fn getPath() -> &Path {
        let path = env::home_dir().unwrap();
        path.push("foo.sqlite3");
        path.as_path()
    }

    fn open() -> SomeDatabase {
        let path = SomeDatabase::getPath()
        SomeDatabase { conn: SqliteConnection::open(path).unwrap() }
    }
}

fn main() {
    let db = SomeDatabase::open();
}

当我尝试编译这段代码时,会出现一个关于缺少生命周期标注的&Path参数的错误。我知道如果它从调用者那里获取了一个引用参数,它将取决于该引用的生命周期。但在这里,我期望这个生命周期将与我正在分配结果的变量相关。

我知道可以显式地添加生命周期,但我不知道如何在这种情况下应用它们。编译器建议尝试使用'static生命周期,但据我所知,在这里并没有意义,因为该函数返回值的来源不是静态的。

现在,仅仅为了尝试编译其余代码,我将返回类型从&Path更改为PathBuf并在open()中调用了as_path()。这导致编译器输出以下错误:

src\main.rs:22:30: 22:52 error: the trait `core::marker::Sized` is not implemented for the type `[u8]` [E0277]
src\main.rs:22         SomeDatabase { conn: SqliteConnection::open(path).unwrap() }
                                            ^~~~~~~~~~~~~~~~~~~~~~
src\main.rs:22:30: 22:52 note: `[u8]` does not have a constant size known at compile-time
src\main.rs:22         SomeDatabase { conn: SqliteConnection::open(path).unwrap() }
                                            ^~~~~~~~~~~~~~~~~~~~~~

SqliteConnection::open() 返回一个 Result<SqliteConnection, SqliteError>,而 SqliteConnection 内部唯一的字段是一个 RefCell。因此,我不明白这个关于字节数组的错误从哪里来。

那么,为什么事情没有按照我的预期工作,最Rusty的编写代码方式是什么?

1个回答

15
在第一种情况下,您正在创建一个值,然后尝试返回对它的引用。但由于您没有将该值存储在任何地方,因此它会在函数结束后被销毁。如果允许这样做,它将是一种use-after-free漏洞。
建议返回&'static Path的原因是因为该函数没有参数化任何生命周期,因此您可以确定唯一的生命周期是'static,它能够超越任何想使用返回值的东西。
您需要直接返回PathBuf而不是&Path
我不太确定为什么会出现[u8]大小的错误。
您完全不需要调用"as_path()"。 SqliteConnection :: open 需要一个实现 AsRef<Path>的值(AsRef有点像 Into ),而PathBuf确实实现了该特征。

我意识到在第一个例子中,我正在返回对函数内创建的某个东西的引用,但如果我将getPath()作为SomeDatabase的成员函数,则编译器足够聪明,可以保持&Path,直到SomeDatabase实例超出范围。 我想知道是否有一种方法告诉编译器引用的生命周期属于调用getPath()的函数(在getPath()是关联函数的情况下)。它的生命周期代码应该持续到在open()中保存引用的变量超出范围。 - Austin Wagner
还要补充一点,如果我省略 as_path(),就会出现类型不匹配的错误:"expected '&_', found 'std::path::PathBuf' (expected &-ptr, found struct 'std::path::PathBuf')。 - Austin Wagner
1
@AustinWagner 变量 pathgetPath() 方法的函数体中定义,其生命周期在该函数体的作用域结束时结束,除非您返回它。无法避免这个规则。在您的情况下,没有其他选择,必须返回 getPath() 函数的 PathBuf - Levans
1
@AustinWagner 如果我将getPath作为成员函数[...]编译器会一直持有&Path,直到实例[...]超出范围 - 这是完全不正确的。你面临的问题是相同的,即局部变量在函数返回时被删除,因为没有任何东西拥有它。 - Shepmaster
SqliteConnection::open 接受一个实现了 AsRef 的类型的引用作为参数 —— 请注意,它实际上接受的是 实现了 AsRef 的类型的引用。我认为这可能是出现大小错误的原因。这似乎是库方面一个奇怪的决定... - Shepmaster
@Shepmaster,当我尝试将getPath更改为成员函数进行测试时,我一定做错了什么,但是看起来你是正确的。我的假设是,当返回引用时,编译器可以将变量提升到调用者的范围内,这显然是错误的。我将此标记为已接受的答案,因为答案和评论包含足够的信息,使其对其他有相同误解的人有用。 - Austin Wagner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接