【问题标题】:Why I get "temporary value dropped while borrowed" if I assign, but not when passing via function?如果我分配,为什么我得到“借用时临时价值下降”,但在通过函数传递时却没有?
【发布时间】:2020-11-05 21:34:59
【问题描述】:

我对 Rust 很陌生。我主要有 C 和 C++ 方面的经验。

来自 lol_html crate 示例的代码有效。

use lol_html::{element, HtmlRewriter, Settings};

let mut output = vec![];

{
    let mut rewriter = HtmlRewriter::try_new(
        Settings {
            element_content_handlers: vec![
                // Rewrite insecure hyperlinks
                element!("a[href]", |el| {
                    let href = el
                        .get_attribute("href")
                        .unwrap()
                        .replace("http:", "https:");

                    el.set_attribute("href", &href).unwrap();

                    Ok(())
                })
            ],
            ..Settings::default()
        },
        |c: &[u8]| output.extend_from_slice(c)
    ).unwrap();

    rewriter.write(b"<div><a href=").unwrap();
    rewriter.write(b"http://example.com>").unwrap();
    rewriter.write(b"</a></div>").unwrap();
    rewriter.end().unwrap();
}

assert_eq!(
    String::from_utf8(output).unwrap(),
    r#"<div><a href="https://example.com"></a></div>"#
);

但是如果我将 element_content_handlers vec 移到外面并分配它,我会得到

借用时临时价值下降

对于 let 行:

use lol_html::{element, HtmlRewriter, Settings};

let mut output = vec![];

{
    let handlers = vec![
                // Rewrite insecure hyperlinks
                element!("a[href]", |el| {
                    let href = el
                        .get_attribute("href")
                        .unwrap()
                        .replace("http:", "https:");

                    el.set_attribute("href", &href).unwrap();

                    Ok(())
                }) // this element is deemed temporary
            ];

    let mut rewriter = HtmlRewriter::try_new(
        Settings {
            element_content_handlers: handlers,
            ..Settings::default()
        },
        |c: &[u8]| output.extend_from_slice(c)
    ).unwrap();

    rewriter.write(b"<div><a href=").unwrap();
    rewriter.write(b"http://example.com>").unwrap();
    rewriter.write(b"</a></div>").unwrap();
    rewriter.end().unwrap();
}

assert_eq!(
    String::from_utf8(output).unwrap(),
    r#"<div><a href="https://example.com"></a></div>"#
);

我认为该方法拥有向量的所有权,但我不明白为什么它不适用于简单的分配。我不想先声明所有元素。我希望有一个简单的成语让它拥有所有元素。

编辑: 编译器建议在行前绑定元素,但是如果我有很多元素怎么办?例如,我想避免命名 50 个元素。有没有办法在不绑定所有元素的情况下做到这一点?还有为什么临时的生命周期在 vec 内部结束!在 let 绑定的情况下调用,但不是在我放置 vec 时调用!在新构造的结构内部传递给一个方法?最后一个问题对我来说很重要。

【问题讨论】:

    标签: rust borrow-checker ownership borrowing


    【解决方案1】:

    当我第一次尝试重现您的问题时,我发现try_new 不存在。它已在最新版本的 lol_html 中被删除。将其替换为new,您的问题没有重现。不过,我能够使用 v0.2.0 进行复制。由于问题与宏生成的代码有关,我尝试了cargo expand(您需要安装的东西,see here)。

    这是let handlers = ... 在 v0.2.0 中扩展的内容:

    let handlers = <[_]>::into_vec(box [(
        &"a[href]".parse::<::lol_html::Selector>().unwrap(),
        ::lol_html::ElementContentHandlers::default().element(|el| {
            let href = el.get_attribute("href").unwrap().replace("http:", "https:");
            el.set_attribute("href", &href).unwrap();
            Ok(())
        }),
    )]);
    

    这是它在 v0.3.0 中扩展的内容

    let handlers = <[_]>::into_vec(box [(
        ::std::borrow::Cow::Owned("a[href]".parse::<::lol_html::Selector>().unwrap()),
        ::lol_html::ElementContentHandlers::default().element(|el| {
            let href = el.get_attribute("href").unwrap().replace("http:", "https:");
            el.set_attribute("href", &href).unwrap();
            Ok(())
        }),
    )]);
    

    忽略第一行,vec宏是怎么回事!展开。第二行显示了版本生成的差异。第一个借用 parse 的结果,第二个取一个 Cow::Owned 。 (Cow 代表写时复制,但它更普遍地适用于任何你想要通用的东西,而不是借用或拥有的东西。)。

    因此,简短的回答是用于扩展为不属于自己的东西的宏,现在它可以了。至于为什么不用单独赋值就可以工作,那是因为 Rust 自动为你创建了一个临时变量。

    当在大多数 place 表达式上下文中使用值表达式时,会创建一个临时未命名的内存位置,并初始化为该值,并且表达式的计算结果会改为该位置,除非提升为静态

    https://doc.rust-lang.org/reference/expressions.html#tempora...

    最初 rust 为您创建了多个临时对象,所有这些都对相同的范围有效,即调用 try_new 的范围。当您将向量分解为自己的分配时,将为元素创建临时!只对向量赋值的范围有效。

    我查看了元素的git blame! lol_html 中的宏,他们做出了改变,因为有人提出了一个本质上是你的问题的问题。所以我会说这是一个泄漏抽象中的错误,而不是您对 rust 的理解的问题。

    【讨论】:

    • 谢谢!有没有关于像 Cow::Owned 这样复杂的文章的好文章?有没有办法将每个元素包装在某个东西中以使其与旧版本一起使用?基本上有没有办法在一个带有未命名元素的语句中创建一个命名的 vec?
    • @HadrianWęgrzynowski 就 Cow 而言,这是一种优化,可让您避免克隆。它基本上包含拥有或借来的东西。无论哪种方式,您都可以轻松地获得对内容的引用,如果您要求它提供一个拥有的值,它将在拥有的案例中noop并在借用的案例中克隆。使用旧版本的解决方案是提交错误报告,他们的宏是错误的。您可以使用未命名的元素创建命名 vec,但不能使用临时引用创建命名 vec。
    【解决方案2】:

    您正在向量内创建一个临时值 (element)。这意味着在向量内部创建的值只存在于向量内部短暂的生命周期内。在向量声明的末尾,该值被释放,这意味着它不再存在。这意味着在vec![] 内部创建的价值只存在于vec![] 内部短暂的生命周期内。在vec![]的末尾,值被释放,意味着它不再存在:

    let handlers = vec![
     ______
    |  
    |    element!("a[href]", |el| {
    |        let href = el.get_attribute("href").unwrap().replace("http:", |"https:");
    |        el.set_attribute("href", &href).unwrap();
    |        Ok(())
    |    }),
    |______ ^ This value is temporary
    ]; > the element is freed here, it no longer exists!
    
    

    然后您尝试使用不存在的值创建HtmlRewriter

    Settings {
        element_content_handlers: handlers,
        // the element inside of `handlers` doesn't exist anymore!
        ..Settings::default()
    },
    

    显然,借用检查器发现了这个问题,您的代码无法编译。

    这里的解决方案是将该元素绑定到带有let的变量:

    let element = element!("a[href]", |el| {
        let href = el.get_attribute("href").unwrap().replace("http:", "https:");
        el.set_attribute("href", &href).unwrap();
        Ok(())
    });
    

    然后创建向量:

    let handlers = vec![element];
    

    现在,该值绑定到一个变量 (element),因此它的寿命足够长,以后可以在 HtmlRewriter::try_new 中借用

    【讨论】:

    • 您的解决方案是编译器提出的,但是如果我有很多元素怎么办?例如,我想避免命名 50 个元素。有没有办法在不绑定所有元素的情况下做到这一点?还有为什么它在 let 绑定的情况下结束,而不是当我将向量放入传递给方法的新构造的结构中时?
    • 我在答案中提到了第二个问题,但为了清楚起见,我在问题中添加了部分评论。
    【解决方案3】:

    当您创建某些东西时,它会被绑定到可能的最内层范围,以便跟踪其生命周期。在更高范围内使用 let 绑定会将值绑定到该范围,从而延长其生命周期。如果您要创建很多东西,然后对它们应用一个操作(例如,将它们传递给另一个函数),那么创建一个值向量然后对它们应用转换通常是有意义的。例如,

    let xs = (0..10).map(|n| SomeStruct { n }).map(|s| another_function(s)).collect();
    

    这样您就不需要将SomeStruct 对象显式绑定到任何东西。

    【讨论】:

    • 为什么,如果它是最内层范围,let hs = vec![elements!(...), ...]try_new(handlers: vec![elements!(...), ...] 不同?对于这两个表达式,最里面的范围是 vec![] 范围。我打算有多个元素,然后在将 vec 传递给 try_new 之前动态地向 vec 添加更多元素。
    • 在第一种情况下,您将 vec 宏的结果绑定到外部范围内的名称。在第二个中,您将在 try_new 调用中临时生成一个向量。
    • 有没有办法在 vec 中有特定的元素并且:不命名它们,不推送到 vec?那么如何在将 vec 传递给 try_new 之前将其放在外面进行处理,同时避免绑定所有元素,因为我不想命名它们。
    • 最好的方法可能是获取任何生成元素并将其打包为Iterator,使用mapfilter 等函数执行您可能需要的任何中间步骤,然后@987654330 @它最后变成一个向量(类似于答案中已经存在的简单示例)。
    • 我的问题是我在代码中手动创建了多个元素。同样在这一步和把它交给 try_new 之间,我想用额外的“生成”元素来扩展 vec。所以在伪代码中:let mut handlers = vec![ element!("a", |el| {...}), element!("img", |el| {...}), element!("video", |el| {...}), ...20 more ]; for ... { ... handlers.push(elem); } try_new(handlers); 在这个阶段我可能会先绑定所有元素,但这很烦人,因为我必须为它们命名,任何更改都会有两个地方。这似乎是一个限制。
    猜你喜欢
    • 1970-01-01
    • 2021-03-05
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-24
    相关资源
    最近更新 更多