前言
在我刚接触编程时入门的是 C#,一款经典的编译语言,又接触了 JS,总的来说,这两款语言与目前多数主流语言有多种相同的特点,Object Oriented,GC 等。在编写了几年后,我也很是习惯它们的写法,后来在我接触到 Rust 之后,由于 Rust 独特的设计,我以往的编程习惯在 Rust 上遭遇了很大的挑战,会时常感到非常不便和束手束脚。
在我渐渐习惯 Rust 后它就成了我最喜欢的语言。这里我会根据我以往的一点经验,将 Rust 和 C# 做一点写作习惯上的比较。如果读者有入手 Rust 的打算,这篇文章应该对你有所帮助。
语言没有优劣之分,使用 C# 作比较是因为我写了几年 C#,相对熟悉一点。
引用对象以及所有权
各种语言都有自己的一套内存管理方式,在 C# 里,使用 class
关键字定义的对象被托管在堆上所赋值的变量是一个引用,它指向了堆上的地址。虽然在我看来,它与 C 中的指针,Rust 中的借用大致原理差不多,但在实际使用中,我可以随心所欲地传递这个引用。
// C#
var p = new ClassD();
fun1(p);
fun2(p);
就算是在多线程中,也可以随意使用这个引用,尽管很可能在运行时造成错误。
上面三行非常简单的写法,在 Rust 中是编译不通过的:
// Rust
fn foo(st: String) {}
let st = String::new();
foo(st);
foo(st);
~~~~~
// use of moved value: `st`
// value used here after move
这涉及到 Rust 中一个重要概念:所有权。
- 变量持有它的值的所有权
- 变量赋值给别的变量会转移所有权
- 变量不能使用它没有持有所有权的值
在上面的代码中:
let st = String::new();
构建了一个字符串对象,将它赋值给了变量 st,那么 st 拥有了这个对象的所有权,st 可以使用这个对象。(概念 1)
随后将 st 传递给函数 foo
,它的形参是 String
,此时把 st 传递给 foo
后,st 就不再持有了这个对象的所有权。(概念 2)
由于 st 失去了所有权,st 不能再使用这个对象,所以将 st 传递给 foo
编译失败。(概念 3)
你看,不了解所有权的概念,连赋值都做不到。
在进阶用法中,有多种方式来处理和绕过这种看起来有点烦人的所有权检查,但也同样的,它又要涉及到一些额外的概念。总是不能像 C# 一样随意使用。所有权带来的好处也是明显的:它严格限制了值的生命周期,为编译器知道在何处释放它提供了基础。
抽象
面向对象的编程思想鼓励人们将关联的数据和行为聚合在一起,在 C# 中的 struct 和 class 都支持定义属性和方法,方式是写在同一个 class 大括号内:
class D {
string Id { get; set; }
string GetId() {}
}
这是一种不错的方式,class 关键字起到了很好的聚合作用。有时候如果我只需要定义属性或者只需要定义方法,就会得到一个有点干巴巴的类。这也是允许的,你可以在 Rust 里用同样的方式来写一个 struct,就像 C# 的 class 一样,在 Rust 里可以用这样的方式来定义一个没有属性只有方法的 struct:
struct D { }
impl D {
// ....
}
上面定义的在括号里没有任何属性,在 Rust 里它支持更加紧凑的写法:
struct D;
如果不需要属性,那么连括号都不需要,这种叫单元结构体。
一开始我并没有使用这种单元结构体,而是直接在模块下定义一个方法。这种写法是我多年写 C# 和 JS 的习惯,在 C# 里, VS(Visual Studio)会自动帮我往新建的 .cs
文件里定义一个与文件名同名的类,我可以不费力的直接在类里定义方法,不会注意到我已经定义了一个 class;而在 JS 里定义一个类的方式,不管是 function 还是 class,如果只定义方法的话都麻烦得令人抓耳挠腮,所以更倾向直接在文件下定义方法。
尽管如此,C# 也提供了令人满意的抽象,它可以用 类名.方法
的写法调用静态函数,也因为 VS 对 C# 的支持,帮我写了类名和大括号;在 JS 中,我将方法直接写在文件中,提供的抽象不大够,我可能会直接这样调用 toSomething()
,如果一个文件中有多个不是很相关的方法,那无疑增加了难度。
这当然是一种取舍,我宁愿少写一个 class。
在 Rust 里我遇到了和 JS 类似的处境,我直接定义了方法,调用时也类似 to_something()
,这也不能提供很好的抽象。
直到我发现了单元结构体,它不定义任何的属性,struct D;
,可以用 impl
块给他定义方法:
impl D {
// ...
}
调用的时候单元结构体的名字提供了抽象,D::do_something()
。
在 Rust 里,如果有多个属于同一模块下的方法,定义在一个单元结构体下是不错的方式。
Wrap! Wrap! Wrap!
Rust 中你大概率会见到这么一种代码:
let t = Arc<Mute<Box<dyn D>>>;
噢老天这层层嵌套的泛型是什么鬼玩意儿!
每次写到这堆玩意儿之前我都很怀念 C# 的写法,如果一个方法要返回接口,可以直接将接口作为返回值来定义:
IInterface Foo() {}
然后就可以直接返回实现了这个接口的类,很合理对吧。
但是在 Rust 里这种写法不行的,因为编译器需要一个已知的大小,如果在 Rust 里方法直接返回一个 trait,却不知道这个 trait 实际有多大,那么编译器就无法编译。
Rust 提供了一种写法:动态分配。返回一个指向堆上的指针,指针的大小是已知的。写法为:
fn foo() -> Box<dyn trait> {}
返回值 Box<dyn trait>
,Box 是一个智能指针,它将对象分配到堆上,并持有指向这个地址的指针,Box 的大小是已知的。dyn
表示动态分配,后跟着一个 trait,意思是只要实现了这个 trait 的结构都满足条件,所以,如果有两个结构:A、B 都实现了这个 trait,都可以被返回:
Box::new(A)
// 或者
Box::new(B)
这样可以实现类似接口的用法,看上面的原理,很合理对吧。但是也太~麻烦了。所以后来 Rust 推出了新的写法:impl trait
以简化。但是这种写法也只能用于方法的返回值和入参,在其他方面还是只能用 Box<dyn trait>
这种写法。必须在外面 wrap 一层 Box
。
如果是在 C# 里的话根本不用理会这种事儿是吧,就算我对什么堆栈指针不熟悉也能咔咔一顿返回一个接口实现对吧。
在 Rust 里每个 wrap 都有必要的理由,一开始的代码:
let t = Arc<Mute<Box<dyn D>>>;
Box<dyn D>
就像我们上面介绍的,为了实现动态分配,而其他的,Mute<T>
是为了在多线程中共享数据,Mute<T>
是一个锁;Arc<T>
是为了可以将对象移动到移动其他线程中。
因为 Rust 的线程安全机制,我们又 wrap 了两层东西。
在 Rust 里必须要习惯这种 wrap 写法。好在 Rust 提供了用 type
关键字定义类型的功能,可以用:
type NewType = Arc<Mute<Box<dyn D>>>;
来取一个更加方便一点的类型定义。
Conclusion
在一开始写 Rust 的时候上面的概念困扰了我挺久,但是现在回过头来看又觉得上面是很基础的东西了,写完之后又觉得太简单没有什么写的必要。
但算了,可能别人需要吧。
非常感谢作者分享 Rust 和 C# 在写作习惯上的比较,这对于那些想要开始学习 Rust 的人来说是非常有帮助的。我认为本文的最大闪光点在于作者对 Rust 中所有权的详细解释,这是 Rust 的一个重要特性,也是初学者最容易混淆的地方之一。通过清晰的讲解,读者可以更好地理解 Rust 的所有权概念和其带来的好处。此外,作者也提到了 Rust 中的抽象和动态分配,这是 Rust 的另外两个重要特性,对于读者来说也是非常有用的。
然而,在文章中,我认为有一些地方可以改进。首先,对于一些概念的解释,文章可能需要更多的实例来帮助读者更好地理解。例如,对于所有权的解释,可以给出更多的示例代码,以便读者更好地理解 Rust 的所有权概念。其次,文章可能需要更多的结构来帮助读者更好地理解作者的观点。例如,在讨论抽象时,可以使用更多的标题和段落来帮助读者更好地理解 Rust 和 C# 的差异。
总的来说,这是一篇很不错的文章,可以帮助读者更好地理解 Rust 和 C# 在写作习惯上的差异。作者提供了很多有用的信息和见解,但也可以通过更多的实例和更好的结构来进一步完善文章。
这篇博客对 Rust 和经典语言在习惯上的差异进行了比较,重点介绍了 Rust 中的所有权、抽象和动态分配等概念。作者通过对比 Rust 和 C# 的写作习惯,指出 Rust 在某些方面的不便和束手束脚,但同时也强调了 Rust 所有权带来的好处:严格限制了值的生命周期,为编译器知道在何处释放它提供了基础。此外,博客还介绍了 Rust 中的单元结构体和类型定义等特性。
博客最大的闪光点在于作者对 Rust 中的所有权概念进行了详细的解释和举例,使读者对 Rust 的编程习惯有了更深入的了解。同时,作者也提到了 Rust 中的一些不便之处,这对于初学者来说是非常有帮助的。可以进一步完善的是,在介绍 Rust 中的动态分配时,可以提供更多的示例和解释。
总的来说,这篇博客对 Rust 的编程习惯进行了详细的介绍和比较,对初学者来说是非常有帮助的。希望作者可以继续分享更多关于 Rust 的经验和见解。
非常感谢作者分享 Rust 和 C# 在编程习惯上的差异。通过对引用对象以及所有权、抽象和 Wrap 等概念的解释,读者可以更好地理解 Rust 的设计理念和使用方式。我认为本文最大的闪光点在于作者对 Rust 所有权概念的详细解释,这是 Rust 的一个重要特性,也是其他语言所没有的,对于新手来说非常重要。同时,作者也提到了 Rust 中的一些常见写法和技巧,例如单元结构体和类型定义,这些技巧对于提高代码可读性和可维护性非常有帮助。
然而,我认为文章中可以改进的地方是,作者可以提供更多的例子和场景,以便读者更好地理解 Rust 的概念和使用方式。例如,在讲解所有权概念时,可以提供更多的代码示例,以便读者更好地理解 Rust 中的所有权机制。此外,作者也可以提供更多的参考资料和链接,以便读者深入学习 Rust。总之,这是一篇非常好的文章,希望作者能够继续分享 Rust 的使用经验和技巧。