Rust中的Traits与Haskell中的Typeclasses:抽象之争

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么要比较Rust和Haskell?

\n\n

如果你曾经在Stack Overflow上寻找过关于"Rust中的traits和Haskell中的typeclasses有什么区别?"这个问题的答案,那么你就会知道:这是一个热门话题。成千上万的浏览量,数十个投票——开发者社区确实想了解这两种机制有何不同。

\n\n

乍一看,它们就像兄弟一样相似:Rust中的traits和Haskell中的typeclasses都允许为不同类型定义通用行为。但如果你深入挖掘,就会发现它们的哲学、实现和实际应用属于不同的世界。

\n\n

在本文中,我们将通过真实的Rust代码示例(并穿插一些Haskell作为对比)来剖析关键区别。最后,您将清楚地理解:何时使用traits,以及何时应考虑类似typeclass的方法。

\n\n

简要概述:什么是Rust中的traits?

\n\n

Traits在Rust中是一种声明一组方法的方式,这些方法必须为特定类型实现。这是泛型编程(generics)和Rust多态的基础。

\n\n

一个简单的例子:假设我们希望不同的类型能够“打招呼”。

\n\n
trait Greeter {\n    fn greet(&self) -> String;\n}\n\nstruct Person {\n    name: String,\n}\n\nimpl Greeter for Person {\n    fn greet(&self) -> String {\n        format!("你好,我叫{}!", self.name)\n    }\n}\n\nstruct Robot {\n    model: String,\n}\n\nimpl Greeter for Robot {\n    fn greet(&self) -> String {\n        format!("哔哔,型号:{}", self.model)\n    }\n}\n\nfn say_hello(item: &impl Greeter) {\n    println!("{}", item.greet());\n}\n\nfn main() {\n    let person = Person { name: "安娜".into() };\n    let robot = Robot { model: "R2-D2".into() };\n    say_hello(&person);\n    say_hello(&robot);\n}
\n\n

这里,Greeter是一个trait。Person和Robot类型以各自的方式实现了它。函数say_hello接受任何实现了Greeter的类型。这就是通过traits实现的多态。

\n\n

简要概述:什么是Haskell中的typeclasses?

\n\n

Typeclass在Haskell中是一个接口,定义了一组可以为不同类型实现的函数。听起来很熟悉,对吧?

\n\n

以下是在Haskell中类似示例的样子:

\n\n
class Greeter a where\n    greet :: a -> String\n\ndata Person = Person { name :: String }\ndata Robot = Robot { model :: String }\n\ninstance Greeter Person where\n    greet (Person n) = "你好,我叫" ++ n ++ "!"\n\ninstance Greeter Robot where\n    greet (Robot m) = "哔哔,型号:" ++ m\n\nsayHello :: Greeter a => a -> IO ()\nsayHello item = putStrLn (greet item)\n\nmain = do\n    sayHello (Person "安娜")\n    sayHello (Robot "R2-D2")
\n\n

语法不同,但逻辑相同。然而,Rust中的traits与Haskell中的typeclasses的区别正是从这里开始的。

\n\n

关键区别#1:单态化 vs 装箱(Box)

\n\n

最根本的traits和typeclasses之间的区别在于编译器如何处理泛型代码。

\n\n

Rust:单态化

\n\n

当你编写fn say_hello(item: &impl Greeter)时,Rust编译器会为每个具体类型创建函数的单独副本。这被称为单态化。结果是:零成本抽象(zero-cost abstraction)——你不为你没有使用的东西付费。但代码大小可能会增长。

\n\n

Haskell:装箱(默认)

\n\n

在Haskell中,typeclass通常通过方法字典(vtable)来实现。编译器传递一个隐式参数——一个包含函数指针的表。这被称为装箱。你为

博客

书籍推荐