首页 > 解决方案 > 为什么我可以毫无错误地转换为另一个引用结构?

问题描述

我写了一个例子,运行没有编译器错误

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
    }
}

标签: rust

解决方案


编译器通常不允许在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。(更多关于dyntrait 对象的关键字)总是安全的是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,将它指向的内存视为特征。


推荐阅读