rust - 为什么我可以毫无错误地转换为另一个引用结构?
问题描述
我写了一个例子,运行没有编译器错误
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 有 text 方法, struct 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
块之外编写任何不安全的代码,但编译器和语言本身存在已知的健全性问题。一些 crate 尤其是标准库依赖于底层unsafe
实现,以提供安全的抽象。
你不是 100% 受到保护的,就像在任何语言中一样,但实际上你可以确定,如果安全的 rust 程序编译,它将在没有未定义行为的情况下工作。
您的代码非常安全,因为它编译时没有错误。
这部分代码试图解决的核心问题是方法调用的歧义。
(&span as &rcdom::Node).text();
(&span as &Handle).text()
考虑我是否将其更改为span.text()
. 编译器应该调用什么方法?Handle::text
? rcdom::Node::text
? Rust 编译器没有规则来决定在这种特殊情况下调用什么。
我们有两个选择。首先是使用完全限定的语法
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
trait 对象的关键字)总是安全的是A
实现 trait TraitN
。
特征对象是无大小的,这意味着编译器无法理解它应该为特定特征对象分配多少内存,因为它可以由多种类型实现并且仅与它的实现者一起存在。实现特征的不同类型之间的大小可能会有所不同。
因此,您不能A
直接投射。即使编译器知道 trait 对象A
在这种情况下总是存在的,trait 对象在本质上是没有大小的。
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()
您本质上是在擦除 type of A
,将它指向的内存视为特征。
推荐阅读
- events - 如何向 Splunk 中的事件添加自定义字段?
- sumo - 如何在 SUMO 模拟中获得角速度
- php - Guzzle *70 recv() 失败(104:对等方重置连接)
- javascript - 如何使用 for 循环在 jquery 中追加数据?
- ruby-on-rails - 使用不同的外键引用相同的模型两次或创建多对多关联
- apache-spark - 在提交通过 pyspark 代码连接 hbase 的 spark 作业期间获取“IOException:Broken pipe”
- spring-boot - What is the proper dependency configuration for Vaadin CssLayout
- excel - 使用 Excel 创建 Visio ER 图
- mysql - 将数据库数据从 express.js 服务器传递到 react.js 组件
- c# - Using WPF/MVVM, how do I pass the SelectedIndex value of a Listbox to my ViewModel?