从 Trait 的翻译说起

近日在知乎闲逛时,和某位抱怨《Rust 编程之道》的答主起了争执:她认为此书将 Trait 翻译成“特型”是极糟糕的译法,足以媲美遗臭万年的鲁棒性(Robust),而笔者对此有不同意见。今天借这个话题谈谈笔者的一些思考,希望能起到抛砖引玉的效果。

笔者没读过《Rust 编程之道》,因此本文不涉及对此书的评价。

翻译问题

为什么“鲁棒性”会招致如此多的反对?概因 Robust 本可信达雅地翻译成“稳健性”,译者却偏偏选择了令人费解的“鲁棒性”,平白增加了理解门槛。default 翻译成“缺省”也是同样的问题。相较之下,Trait 的翻译要有争议的多:

首先,Trait 应不应该翻译?这个问题见仁见智。对于评论留言等场合,直接使用英文也无伤大雅;但对于教科书和文章,使用中文更显正式。考虑到 Trait 是一个常用的专有术语,有一个合适的中文表述是非常重要的。

其次,Trait 应该如何翻译?Trait 一词的原意是特质、特性和特征,中文社区似乎倾向于译为“特征”。问题是特征的语感不好,“高阶特征约束”读起来就不怎么顺口。如果一定要从中选一个译名,我推荐学习 Scala 官方文档,将 Trait 译为“特质”。

虽然术语 Trait 来自 Scala,但 Rust 的 Trait 更接近 Haskell 的 Typeclass:如果说 type/class 是对 value/object 的抽象,那么 trait(typeclass)/interface 就是对 type/class 的抽象。类型可以视为一组值的集合,特型则可以视为一组类型的集合。所以 trait 可以用来约束泛型,而类型可以用来约束变量(和函数参数)。具体来说,Rust : 左边是泛型名,右边是它的特型;: 左边是变量名,右边是它的类型。

泛型参数对应的其实是变量或形参,变量 v 是调用时具体值的标识符,泛型 T 是为给调用时确定的具体类型起了一个标识符。例如,调用泛型函数时可以通过 Turbofish 语法指明泛型参数 T 的具体“值”:

1
2
3
4
5
6
7
8
fn foo<T: Clone>(v: T) -> T {
    let y = v.clone();
    y
}

fn main() {
    println!("{}", foo::<usize>(128));
}

foo::<usize>(128) 调用把 usize 传给泛型参数 T,把 128 传给值参数 x。这在类型为一等公民的 Zig 中更明显:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const std = @import("std");

fn foo(comptime T: type, v: T) T {
    const y = v;
    return y;
}

pub fn main() !void {
    // 函数调用时传入的实参为 usize 和 128
    const result = foo(usize, 128);
    std.debug.print("{d}\n", .{result});
}

“特型”指一组具有指定特点的类型,“特型”可以理解为“更高维”的类型,注意笔者不是在说 HKT。这样翻译有利于初学者在类型、泛型和 Trait 之间建立联系,还便于理解 Trait Object,因为特型对象就是把特型当成类型用,直接用特型约束变量(对象),这和 Go 的 Interface 类似。

组合优于继承

C++、Java 的继承,实际上是类(Class)或类型(Type)的继承:子类继承了父类的数据和方法。Rust 倾向于另一种抽象方式:不同的特型,为每种类型实现它们

加载评论