Rust支持既存类型的理解

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

内容简介:最近利用周末时间来学习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


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

查看所有标签

猜你喜欢:

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

数据驱动设计

数据驱动设计

[美]罗谢尔·肯(RochelleKing)、[美]伊丽莎白F.邱吉尔(Elizabeth F Churchill)、Caitlin Tan / 傅婕 / 机械工业出版社 / 2018-8 / 69.00元

本书旨在帮你了解数据引导设计的基本原则,了解数据与设计流程整合的价值,避免常见的陷阱与误区。本书重点关注定量实验与A/B测试,因为我们发现,数据分析与设计实践在此鲜有交集,但相对的潜在价值与机会缺大。本书提供了一些关于在组织中开展数据实践的观点。通过阅读这本书,你将转变你的团队的工作方式,从数据中获得大收益。后希望你可以在衡量指标的选择、佳展示方式与展示时机、测试以及设计意图增强方面,自信地表达自......一起来看看 《数据驱动设计》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具