前言
花了十天看完了Rust Programming Language 这本书,觉得真是受益匪浅;还就方法重载、继承之类的OOP特性在社区论坛和大佬们讨论了一下,让我重新思考了一下一些在Java里曾经以为是理所当然的东西。现在把这些东西整理一下,当做复习了。
参考资料
“The book”- Rust Programming Language
Rust Forum帖子:
Is there a simple way to overload functions?
How to implement inheritance-like feature for Rust
目录
- Rust和重载Overloading
- Builder Pattern
- Rust和继承Inheritance
Rust和重载Overloading
由于习惯了C++和Java的重载,原来一直觉得重载就应该是OOP里天经地义的,直到遇到了Rust
Rust里是没有方法重载的,原因是:
- 实现完全体的Overloading与Rust的静态类型推断不兼容
Rust里有非常方便的静态类型推断,可以搭配插件(比如说idea里的Rust插件)实现实时的静态类型推断并且标注上变量类型,在不失易读性的情况下省事,而不像Python那样完全需要人记住或者推断变量的类型是什么。但是因为Rust的静态类型推断以及可选的类型标注,使得完全的Overloading是不可能的。
完全的Overloading,像Java里面的,可以根据方法的Argument的类型、数量,以及返回值的类型对同名方法进行重载,然而如果一个同名方法的返回值有很多种,那么就很难推断使用方法的返回值的时候到底是用了哪一个方法以及返回值的类型是什么,特别是当一个变量的类型需要根据下文里如何去使用它来推断类型的时候(这样有可能形成一个推断环)
像这样:
fn show(i :u8) { println!("{}",i); }
fn show(i :i8) { println!("{}",i); }
fn main()
{
let i = 8;
show(i); //用哪一个function?
}
- 实现有限的Overloading可以但是没必要
而我在帖子里跟别人讨论,假如我们只允许有限的重载呢?比如说我们只允许根据Argument的数量来重载,返回值的类型必须一样,像这样:
fn add(a: i8, b: i8) -> i8
fn add(a: i8, b: i8, c:18) -> i8
但是这样显而易见的,限制太多,而且作用没有完全体那么大,可以,但是没必要。
为什么需要重载?
- 我们需要应对不同的传递值和返回值类型
已知Rust里有trait
,类似于Java里有interface
,虽然它们不完全相同。引用帖子的回复:Logically if we assume that function name is describing a behaviour, there is no reason, that the same behaviour may be performed on two completelly independent, not simillar types - which is what overloading actually is. If there is a situation, where operation may be performed on set of types, its clear, that they have something in common, so this commoness should be extracted to trait, and then generalizing function over trait is straight way to go
他大概说的是:假定函数描述的是对物体的行为,那么重载实际上是一个行为对多种完全不相干的东西做操作;但是既然行为可以对这么多种东西做类似的操作,那么这么多种东西必然有相似之处,相似之处就应该抽象为trait
,然后再用泛型加trait bound
就好了(泛型+trait bound
可以实现类似于Java接口对象的作用,trait bound
请见The Book) - 我们需要不同数量的Arguement
现在我们已经用泛型加trait bound
解决了Overloading的一大部分需求了,就是基于Argument类型、返回值类型的重载,但是我们的函数、方法需要不同数量的Arguement怎么办?首先想想我们的函数和方法为什么需要不同数量的Arguement?这个估计很多人没认真想过,我原来也是。这个是我个人的答案Maybe functions and methods with defaults? Like the constructor of InputStreamReader in Java, we can just pass an input stream(presuming UTF8 encoding by default) to it or moreover specify the encoding of the input stream, so we need a constructor with only one parameter and another with two parameters.
带默认值的函数或者方法,就像Java里的InputStreamReader
的构造器,我们可以传编码或者用默认的UTF8.
这种情况下,在Rust里确实是个问题,不过只是无关紧要的小问题,我们就直接写好没有默认值的函数/方法,然后再写一个有默认值的函数/方法把默认值和函数调用封装起来就好了。而对于Constructor构造器,更广泛地,我们可以使用Builder Pattern来把一个struct实例像搭积木一样拼起来,见下一章。