1、理解Rust要真正的理解它是一个更好的C++入手
几个必须要搞清楚的高级概念,Rust的Trait和Golang的interface看起来如此相似,到底有什么不同?Rust的属性编程attribute和Java的注解编程有什么不同? 为什么需要phantomdata? 泛型是怎么一回事,和C++的template有什么区别,go又为什么没有?
看起来这些问题不应该是入坑rust 7天的人需要考虑的,并且每个问题似乎都可以展开来写好长一篇。触发我思考这些问题,主要是之前我在一个知乎问题回答里,当时说过Rust也实现了注解编程,马上有人提出反对,这和Java的注解编程完全是两个概念,不能混为一谈。我楞了半天,从形式上看,attribute和annotation真的是一回事,trait和interface也差不多,但确实在实现机制和原理上不一样,所以那位网友反对我的说法,应该是从实现上来说的,他也对,只是我的说法其实也并没有错。
根本的差异在于Rust追求的是编译时类型确定的静态语言,因此它抛弃了大量语言对于反射(go的interface,c++ template,java ioc等)的使用。
2、trait和interface形似神不似
所以第一个要拿出来讨论的就是trait和interface了,前面已经说了,从用户(开发者)的使用体验来看,trait和interface是基本类似的,由于go采用了反射的机制,可以在运行时runtime动态来确定对象的实际类型,所以在代码上显得更简洁优雅elegant:
type Duck interface {
Voice()
}
任何实现了Duck接口的对象,就可以做到“只要它走起来像鸭子,叫起来也像鸭子,他就是一个鸭子”的效果:
func (ducklike *Something) Voice{
...
}
相比而言,Rust的trait需要更多的代码:
impl Duck for Someting {
fn voice(&self) {...}
}
核心不同在于实现机制:Rust是通过编译器在编译的时候就完全推断出(infere)所有的类型,一个对象是否拥有一个方法method都在编译的时候确定,不在运行时通过反射来推断,一方面是让问题提早暴露,另一方面也导致rust的编译速度要比相同大小的go慢的多。
3、atrribute和annotation形似神不似
让我们来看一段代码:
#[derive(Debug, PartialEq)]
pub enum LYSuit {
Nosuit = 0, Clubs = 1, Diamonds = 2, Hearts = 3, Spades = 4
}
#[derive]这一行就给LYSuit这个enum提供了Debug, PartialEq的代码实现,本质上它是用macro展开的形式,在代码里展开成:
impl Debug for LYSuit {
...
}
而看Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。所以这里java的注解是要实现某些特定功能(这一点和rust attribute并无二致),并将其注入到字节码中,这就和attribute相距甚远了。
4、泛型generic和c++ template
可以说二者几乎是一样的,除了Rust提供了一个泛型的绑定概念bound,用于在申明对象时,可以要求模板参数类型必须具备什么样的特性,以减少莫名的错误。拿 https://www.bookstack.cn/read/cpp-to-rust-book/features_of_rust-generics.md 这篇文章的例子来说明:
class
PatientRecord
{
std::string name_;
PatientRecord()
{}
PatientRecord
operator=
(const
PatientRecord
&other)
{
return
*this;
}
public:
PatientRecord(const std::string
&name)
: name_(name)
{
}
};
...
std::vector<PatientRecord> records
这里在line 5将方法 operator=申明成了私有,但这个PatientRecord用于申明Vector没有任何问题:records.push_back(PatientRecord("John Doe"));但如果调用records.erase(records.begin());会报错,说PatientRecord PatientRecord::operator=(const PatientRecord&)'is private,所以问题来了,如果在最后调用某个方法才报错误,为什么不能再变量申明的时候就有一种机制能拒绝呢?
Rust就通过泛型的bound机制,要求作为模板参数的类型必须实现某个trait。具体的bound机制可以搜索加以理解,这里就不展开了。
写作本文的时候golang没有模板,实现这种功能,可以通过接口interface达到一定程度的模拟和近似。使用接口,必须自己编写方法,使用几种方法定义命名类型来反转切片是很尴尬的。编写的方法对于每种切片类型都是完全相同的,所以从某种意义上说,我们只是移动并压缩了重复的代码,我们还没有消除它。虽然接口是泛型的一种形式,但它们并没有向我们提供我们想要的所有泛型。https://www.oschina.net/news/108697/why-golang-generic。有意思的是,golang社区正在认真的考虑添加泛型这一重要功能。
5、为什么需要phantomdata
偷个懒,phatomdata在https://www.jianshu.com/p/8554bbf13a02 这篇文章里解释的很清楚。