Rust支持既存类型的理解

栏目: 编程语言 · Rust · 发布时间: 6年前

内容简介:最近利用周末时间来学习Rust编程,发现新发布的1.26版本,带来了impl Trait is now stable allowing you to have abstract types in returns or in function parameters. e.g. fn foo() -> impl Iterator

最近利用周末时间来学习Rust编程,发现新发布的1.26版本,带来了 impl Trait ,一时对它的写法难以理解,今天又找点资料再温习一下。

impl Trait is now stable allowing you to have abstract types in returns or in function parameters. e.g. fn foo() -> impl Iterator or fn open(path: impl AsRef ).

既存类型

impl Trait 是对 既存类型(Existential types) 的支持,那什么是既存类型?

Existential types are frequently used in connection with record types to represent modules and abstract data types, due to their ability to separate implementation from interface. For example, the type “T = ∃X { a: X; f: (X → int); }” describes a module interface that has a data member named a of type X and a function named f that takes a parameter of the same type X and returns an integer. This could be implemented in different ways; for example:
intT = { a: int; f: (int → int); }  floatT = { a: float; f: (float → int); }
These types are both subtypes of the more general existential type T and correspond to concrete implementation types, so any value of one of these types is a value of type T. Given a value “t” of type “T”, we know that “t.f(t.a)” is well-typed, regardless of what the abstract type X is. This gives flexibility for choosing types suited to a particular implementation while clients that use only values of the interface type—the existential type—are isolated from these choices.
In general it’s impossible for the typechecker to infer which existential type a given module belongs to. In the above example intT { a: int; f: (int → int); } could also have the type ∃X { a: X; f: (int → int); }. The simplest solution is to annotate every module with its intended type, e.g.:
intT = { a: int; f: (int → int); } as ∃X { a: X; f: (X → int); }

从上面wiki的介绍,既存类型相对还是比较容易理解,既存类型早已发明,有着距今约30年的历史。既存类型是用来表达一种抽象类型,它连接record types(如rust中的struct),其实现与接口分离。说白一点,有点像 Java 中interface或 GO 的Interface。

在Rust中,我们可以采用 impl Trait 指定函数的返回类型,而不必指出具体是哪一种类型。例如:

fn foo() -> impl Trait {
    // ...
}

如果是这样,为什么Rust不直接设计为如下,Trait像Java8中interface或GO的Interface,函数返回interface:

fn foo() -> Trait {
    // ...
}

遗憾地是,上面的写法在Rust都不可能编译通过,因为在Rust变量lifetime之说,返回值的lifetime不能悬空,那只能变成这种写法

fn foo3() -> Box<Trait> {
    Box::new(5) as Box<Trait>
}

这样写是不是很繁琐,不过,使用Box 意味着动态分配,我们并非总是希望或需要这样,而 impl Trait 确保了静态分配。这种方法使foo仅能返回同样的类型。

trait Trait {
    fn method(&self);
}

// 表示类型T实现了Trait
impl<T: Sized> Trait for T {
    fn method(&self) {    
    }
}

fn new_foo1() -> impl Trait {
    5  // 返回一个i32类型的值
}

fn new_foo2() -> impl Trait {
    5.0f32  // 返回一个f32类型的值
}

在定义返回闭包的函数时,新的 impl Trait 语法也可以如下使用,闭包函数实现了特性Fn:

fn foo() -> impl Fn(i32) -> i32 {
    |x| x + 1
}

另外, impl Trait 语法还可以用于替代泛型类型的声明,如下例所示,虽然在这种情况下,它定义了一个通用类型,而不是存在类型:

// 之前
fn foo<T: Trait>(x: T) {

// 之后
fn foo(x: impl Trait) {

从上面来看, impl Trait 其实就是一种语法糖而已,在其中语言中司空见惯的用法,由于在Rust的lifetime管理,函数不支持返回抽象类型,简单问题复杂化了。

具体应用

actix是rust实现的一个web框架,它很快就使用到 impl Trait ,如下所示:

[derive(Serialize)]
struct Measurement {
    temperature: f32,
}

fn hello_world() -> impl Responder {
    "Hello World!"
}

fn greet(req: HttpRequest) -> impl Responder {
    let to = req.match_info().get("name").unwrap_or("World");
    format!("Hello {}!", to)
}

fn current_temperature(_req: HttpRequest) -> impl Responder {
    Json(Measurement { temperature: 42.3 })
}

其中Responder是一个Trait,它定义如下:

// https://github.com/actix/actix-web/blob/master/src/handler.rs#L24

pub trait Responder {
    /// The associated item which can be returned.
    type Item: Into<AsyncResult<HttpResponse>>;

    /// The associated error which can be returned.
    type Error: Into<Error>;

    /// Convert itself to `AsyncResult` or `Error`.
    fn respond_to<S: 'static>(
        self, req: &HttpRequest<S>,
    ) -> Result<Self::Item, Self::Error>;

Json是一个struct,它的实现在json.rs文件,也是实现了Responder Trait,在respond_to方法中对T进行了序列化,并生成Result对象

// https://github.com/actix/actix-web/blob/master/src/json.rs#L119

impl<T: Serialize> Responder for Json<T> {
    type Item = HttpResponse;
    type Error = Error;

    fn respond_to<S>(self, req: &HttpRequest<S>) -> Result<HttpResponse, Error> {
        let body = serde_json::to_string(&self.0)?;

        Ok(req
            .build_response(StatusCode::OK)
            .content_type("application/json")
            .body(body))
    }
}

为什么直接返回”Hello World!“与format!(“Hello {}!”, to)也行,它是怎么做到,原因在于在handler.rs中AsyncResult实现From Trait,支持把任一类型转成AsyncResult。

https://github.com/actix/actix-web/blob/master/src/handler.rs#L292

impl<T> From<T> for AsyncResult<T> {
    #[inline]
    fn from(resp: T) -> AsyncResult<T> {
        AsyncResult(Some(AsyncResultItem::Ok(resp)))
    }
}

参考:

[1] https://www.infoq.com/news/2018/05/rust-1.26-existential-types


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Linux二进制分析

Linux二进制分析

[美]瑞安 奥尼尔 / 棣琦 / 人民邮电出版社 / 2017-12-1 / CNY 59.00

二进制分析属于信息安全业界逆向工程中的一种技术,通过利用可执行的机器代码(二进制)来分析应用程序的控制结构和运行方式,有助于信息安全从业人员更好地分析各种漏洞、病毒以及恶意软件,从而找到相应的解决方案。 本书是一本剖析Linux ELF工作机制的图书,共分为9章,其内容涵盖了Linux环境和相关工具、ELF二进制格式、Linux进程追踪、ELF病毒技术、Linux二进制保护、Linux中的E......一起来看看 《Linux二进制分析》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器