我写了一个例子,并且运行时没有编译器错误
use std::collections::HashSet;
use std::error::Error;
use html5ever::rcdom;
use html5ever::rcdom::Handle;
use reqwest;
use soup::{prelude, QueryBuilder};
use soup::prelude::*;
use testquestion::testtrait::Test;
fn main() -> Result<(), Box<Error>> {
let resp = reqwest::get("https://docs.rs/soup/0.1.0/soup/")?;
let soup = Soup::from_reader(resp)?;
let result = soup
.tag("section")
.attr("id", "main")
.find()
.and_then(|section:Handle| -> Option<String> {
section
.tag("span")
.attr("class", "in-band")
.find()
.map(|span:Handle| -> String {
(&span as &rcdom::Node).text();
(&span as &Handle).text()
}
)
});
assert_eq!(result, Some("Crate soup".to_string()));
Ok(())
}
但是我很困惑
(&span as &rcdom::Node).text();
(&span as &Handle).text()
trait NodeExt具有文本方法,并通过构造Node和Handle来实现。但是为什么我可以将结构句柄的引用转换为其他引用(句柄和节点)而没有编译器错误?安全吗?我是锈病的新手。
pub trait NodeExt: Sized {
/// Retrieves the text value of this element, as well as it's child elements
fn text(&self) -> String {
let node = self.get_node();
let mut result = vec![];
extract_text(node, &mut result);
result.join("")
}
}
impl<'node> NodeExt for &'node rcdom::Node {
#[inline(always)]
fn get_node(&self) -> &rcdom::Node {
self
}
}
impl NodeExt for Handle {
#[inline(always)]
fn get_node(&self) -> &rcdom::Node {
&*self
}
}
编译器通常不允许在unsafe
块之外编写任何不安全的代码,但是编译器和语言本身都有已知的soundness issues。一些板条箱,尤其是标准库,在底层依靠unsafe
实现来提供安全的抽象。
您不会像任何语言一样受到100%的保护,但是实际上,您可以确定,如果编译安全的rust程序,它将在没有未定义行为的情况下运行。
您的代码非常安全,因为它编译时没有错误。
这部分代码试图解决的核心问题是方法调用的歧义。
(&span as &rcdom::Node).text();
(&span as &Handle).text()
考虑是否将其更改为span.text()
。编译器应调用什么方法? Handle::text
? rcdom::Node::text
? Rust编译器没有规则可以忽略在这种特殊情况下该调用什么。
我们有两个选择。首先是使用fully-qualified syntax
rcdom::Node::text(&span);
Handle::text(&span)
实际上是rustc
建议的。
error[E0034]: multiple applicable items in scope
--> src/main.rs:20:5
|
20 | A::test();
| ^^^^^^^ multiple `test` found
|
note: candidate #1 is defined in an impl of the trait `Trait1` for the type `A`
--> src/main.rs:12:5
|
12 | fn test() {}
| ^^^^^^^^^
= help: to disambiguate the method call, write `Trait1::test(...)` instead
note: candidate #2 is defined in an impl of the trait `Trait2` for the type `A`
--> src/main.rs:16:5
|
16 | fn test() {}
| ^^^^^^^^^
= help: to disambiguate the method call, write `Trait2::test(...)` instead
第二是强制转换类型。从&A
投射到&dyn TraitN
。 (有关dyn
keyword for trait objects的更多信息)始终是安全的,因为dyn
实现了特征A
。
特质对象没有大小,这意味着编译器无法理解应为特定特征对象分配多少内存,因为它可以由许多类型实现,并且仅与实现者一起存在。大小可能因实现特征的不同类型而异。
因此,您无法直接投射TraitN
。即使在这种情况下编译器知道特质对象始终为A
时,特质对象也不会根据其性质调整大小。
A
您可以借用对
error[E0620]: cast to unsized type: `A` as `dyn Trait1`
--> src/main.rs:20:5
|
20 | (A as Trait1).test()
| ^^^^^^^^^^^^^
|
help: consider using a box or reference as appropriate
--> src/main.rs:20:6
|
20 | (A as Trait1).test()
| ^
的引用,因此您将获得固定大小的引用,并将其转换为同样固定大小的A
(因为它是对引用的引用)。因此,您可以获得类型为&dyn TraitN
的对象,可以轻松进行方法调用,而不会产生歧义。
&dyn TraitN
您本质上是擦除 let a: &dyn Trait1 = &A as &dyn Trait1;
a.test()
的类型,将其指向的记忆视为特征。