为什么我在分配时得到 "temporary value dropped while borrowed",但在通过函数传递时却没有?
Why I get "temporary value dropped while borrowed" if I assign, but not when passing via function?
我对 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 移到外面并分配它,我会得到
temporary value dropped while borrowed
对于 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 时不会调用!在新构造的结构中传递给方法?最后一个问题对我来说很重要。
您正在向量中创建一个临时值 (element
)。这意味着在 vector 内部创建的值仅在 vector 内部短暂的生命周期内存在。在 vector 声明的末尾,该值被释放,意味着它不再存在。这意味着 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!
然后您尝试使用 non-existent 值创建 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
中借用
当您创建某些东西时,为了跟踪其生命周期,它会绑定到可能的最内层范围。在更高范围使用 let
绑定会将值绑定到该范围,从而延长其生命周期。如果您正在创建很多东西,然后对它们应用一个操作(例如,将它们传递给另一个函数),那么创建一个值向量然后对它们应用转换通常是有意义的。例如,
let xs = (0..10).map(|n| SomeStruct { n }).map(|s| another_function(s)).collect();
这样您就不需要将 SomeStruct
对象明确绑定到任何对象。
当我第一次尝试重现您的问题时,我发现 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是怎么来的!扩大。第二行显示了版本生成的差异。第一个借用解析的结果,第二个借用它的 Cow::Owned 。 (Cow 代表写时复制,但它更普遍地适用于任何你想要通用的东西,无论是借用的还是拥有的版本。)。
所以简短的回答是用于扩展到不拥有的东西的宏,现在它拥有了。至于为什么它没有单独的赋值就可以工作,那是因为 Rust 自动为你创建了一个临时变量。
When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead, except if promoted to a static
https://doc.rust-lang.org/reference/expressions.html#tempora...
最初 Rust 为您创建了多个临时文件,所有临时文件都对相同的范围有效,即对 try_new 的调用范围。当您将向量分解为它自己的分配时,为元素创建的临时对象!只对向量赋值的范围有效。
我看了一下 git blame 的元素! lol_html 中的宏,他们进行了更改,因为有人提出了一个本质上是您的问题的问题。所以我想说这是一个漏洞抽象的错误,而不是你对锈的理解的问题。
我对 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 移到外面并分配它,我会得到
temporary value dropped while borrowed
对于 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 时不会调用!在新构造的结构中传递给方法?最后一个问题对我来说很重要。
您正在向量中创建一个临时值 (element
)。这意味着在 vector 内部创建的值仅在 vector 内部短暂的生命周期内存在。在 vector 声明的末尾,该值被释放,意味着它不再存在。这意味着 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!
然后您尝试使用 non-existent 值创建 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
当您创建某些东西时,为了跟踪其生命周期,它会绑定到可能的最内层范围。在更高范围使用 let
绑定会将值绑定到该范围,从而延长其生命周期。如果您正在创建很多东西,然后对它们应用一个操作(例如,将它们传递给另一个函数),那么创建一个值向量然后对它们应用转换通常是有意义的。例如,
let xs = (0..10).map(|n| SomeStruct { n }).map(|s| another_function(s)).collect();
这样您就不需要将 SomeStruct
对象明确绑定到任何对象。
当我第一次尝试重现您的问题时,我发现 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是怎么来的!扩大。第二行显示了版本生成的差异。第一个借用解析的结果,第二个借用它的 Cow::Owned 。 (Cow 代表写时复制,但它更普遍地适用于任何你想要通用的东西,无论是借用的还是拥有的版本。)。
所以简短的回答是用于扩展到不拥有的东西的宏,现在它拥有了。至于为什么它没有单独的赋值就可以工作,那是因为 Rust 自动为你创建了一个临时变量。
When using a value expression in most place expression contexts, a temporary unnamed memory location is created initialized to that value and the expression evaluates to that location instead, except if promoted to a static
https://doc.rust-lang.org/reference/expressions.html#tempora...
最初 Rust 为您创建了多个临时文件,所有临时文件都对相同的范围有效,即对 try_new 的调用范围。当您将向量分解为它自己的分配时,为元素创建的临时对象!只对向量赋值的范围有效。
我看了一下 git blame 的元素! lol_html 中的宏,他们进行了更改,因为有人提出了一个本质上是您的问题的问题。所以我想说这是一个漏洞抽象的错误,而不是你对锈的理解的问题。