「Generics 解密」1-2

526 阅读2分钟

「这是我参与11月更文挑战的第 4 天,活动详情查看:2021最后一次更文挑战


函数以及闭包

一个函数由一个类型定义(函数签名)和一个执行环境(命令式代码块)组成。在转向实现方法之前,我们先从一个简单的函数开始:

struct Line<P> {
    points: (P, P),
}

fn connect<P>(left: P, right: P) -> Line<P> {
    Line {
        points: (left, right)
    }
}

在这里,Point tuple 中使用的类型是在调用connect的地方决定的,因为那是调用Line的地方。如果紧耦合是可以接受的,我们可以指定connect总是处理 Lines and Points,同时仍然允许在 Point的id属性 使用一个通用类型。

fn connect<Id>(left: Point<Id>, right: Point<Id>) -> Line<Point<Id>> {
    Line {
        points: (left, right)
    }
}

我们仍然有一个类型参数,但现在 Point and Line 的类型在这个函数中是紧密耦合的。

和其他函数一样,参数可以写成一个结构体,而函数体可以移到一个方法中。我们不想在函数式与面向对象的代码美学的永恒争斗中占上风,而只是观察函数签名是类型定义。这不是一个微不足道的观察。现在,仅仅指出这一点就足够了。让我们继续使用闭包的通用类型。

/// This is the same Point we used to first demonstrate a generic type.
#[derive(Debug)]
struct Point<Id> {
    id: Id
}

/// This Line is different. We've restricted it to use the Point type but left
/// the Id flexible. Before, this tight coupling was done in the `connect`
// method.
#[derive(Debug)]
struct Line<Id> {
    points: (Point<Id>, Point<Id>),
}

/// This new type contains a closure. While Box is a type, this involves Fn, a
/// trait that uses generic types. We will write our own such trait later.
struct Connector<Id> {
    pub op: Box<dyn Fn(Point<Id>, Point<Id>) -> Line<Id>>,
}

fn main() {
    let connector: Connector<String> = Connector {
        op: Box::new(|left, right| Line {
            points: (left, right),
        }),
    };

    let point_a = Point {
        id: "A".to_string(),
    };
    let point_b = Point {
        id: "B".to_string(),
    };
    let line = (connector.op)(point_a, point_b);

    println!("{:?}", line);
}

在这里,我们建立了一个小小的库,只要再做一点工作,就可以做一些有趣的事情。

如果我们对成对的 Point 进行迭代,我们可以允许用户在记录、修改或过滤它们的同时将它们连接起来。同时,我们的库可以处理迭代的工作方式。或者,我们可以在用户想要的任何大小的组中选择它们,并将它们组合成我们提供的类型,而不是迭代成对并返回线。我们也可以让用户写出他们自己的线、方块、图形或任何他们想要的东西,让他们把它插入一个类型参数(即通用类型)。