为什么我可以毫无错误地转换为另一个引用结构?

问题描述 投票:0回答:1

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

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
    }
}
rust
1个回答
0
投票

编译器通常不允许在unsafe块之外编写任何不安全的代码,但是编译器和语言本身都有已知的soundness issues。一些板条箱,尤其是标准库,在底层依靠unsafe实现来提供安全的抽象。

您不会像任何语言一样受到100%的保护,但是实际上,您可以确定,如果编译安全的rust程序,它将在没有未定义行为的情况下运行。

您的代码非常安全,因为它编译时没有错误。

这部分代码试图解决的核心问题是方法调用的歧义。

                    (&span as  &rcdom::Node).text();
                    (&span as  &Handle).text()

考虑是否将其更改为span.text()。编译器应调用什么方法? Handle::textrcdom::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

(Playground link)

第二是强制转换类型。从&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() 的类型,将其指向的记忆视为特征。

© www.soinside.com 2019 - 2024. All rights reserved.