今天开始,为了切入webassembly,切入Rust;那么问题来了:为什么现在要切入webassembly?为什么选择Rust而不是c/c++、go等语言?众所周知,webassembly还不是很成熟,要应用到生产环境还有很长的路要走,有没有必要这么着急把精力投入到这个领域(Vue3.0,React源码还想去看看)不过个人意见现在是时候了。第一,webassembly是大势所趋,很多人和公司都跃跃欲试,尤其是在游戏领域;其次,对于大多数前端(包括我)来说,webassembly必须学习一门静态语言(如果是c/c++,可以忽略)需要一定的时间学习和积累。那么另一个问题,为什么不选择c/c++或者go等语言呢?C/c++经过几十年生态还是很强大的,学习资料也很多,应用也很广,但是有一个很重要的问题就是历史包袱很重,个人无法在短时间内熟练使用;而go、java等语言,学习成本不高,应用也很广泛。其实切入webassembly确实是有好处的。当webassembly没有完全开发时,gc总是一个障碍。也许3到5年后这个问题就不是问题了。那么Rust有哪些优势呢?它年轻,历史包袱比较小,设计也比较严谨。工具链也更加人性化。Cargo和npm在体验上比较相似,性能也不错。您可以在后端使用节点来做一些性能优化。点但是学习成本还是挺高的,介于c++和go语言之间(个人认为)。基于以上总结,Rust以微弱优势获胜。事实上,rust是Firefox主推的语言。从长远来看,它仍然会无限接近Web开发者,这也是一个潜在的优势。Rust相关中文资料RustProgrammingLanguage(第二版)简体中文版LearnRustbyexampleRustNecronomiconRustMacroBooklet中文版RustCommonDesignPatternsrustTooManyListsLearnrustfirstversionofthedocumentbyimplementinglinkedlists虽然不是latest但相反,它对rust错误处理更加深入和详细。rustreferencerustasync书中会有更多中文资料。Rustmyths的临时值在该语句的末尾被释放。示例代码如下:structCat{pubc:i32}structBar{pubb:i32}structFoo<'a>{bar:&'aBar,cat:&'aCat}fnmain(){let栏:&Bar=&Bar{b:123};让猫:&Cat=&Cat{c:234};让foo:&mutFoo;{foo=&mutFoo{bar:bar,cat:cat};//错误println!("{}",foo.cat.c);}}会直接执行报错:|17|foo=&mutFoo{bar:bar,cat:cat};|^^^^^^^^^^^^^^^^^^^^^^^^^-临时值在这条语句的末尾被释放|||创建一个临时文件,它在仍在使用时被释放18|println!("{}",foo.cat.c);|--------借用以后用这里|=注意:考虑使用`let`绑定来创建一个寿命更长的值,但我稍微更改了代码:{letfoo=&mutFoo{bar:bar,cat:cat};println!("{}",foo.cat.a);}这是正常执行;但是为什么第一种跑不了呢,根据编译器提示,执行rustc--explainE0716,看编译器给的提示(果然rust是教你写代码的编译器);大致总结一下:foo=&mutFoo{bar:bar,cat:cat};大致等同于:foo={//创建一个拥有Foo实例所有权的tmp变量letmuttmp=Foo{bar:bar,cat:cat};//returnreferencetotmp&muttmp}tmp的生命周期显然只在花括号范围内,detach之后就会释放,所以返回的是悬空引用。使用let时,对临时变量的引用,编译器会自动延长临时变量的生命周期。另一种情况,更改主函数的代码:fnmain(){letbar:&Bar=&Bar{b:123};让猫:&Cat=&Cat{c:234};让mutfoo:Foo=Foo{bar:bar,cat:cat};{foo.cat=&Cat{c:666};println!("{}",foo.cat.c)}println!("{}",foo.cat.c);}这段代码能正常运行吗,foo.cat指的是里面的一个临时变量;答案是肯定的,它会起作用。再次更改:fnmain(){letbar:&Bar=&Bar{b:123};让猫:&Cat=&Cat{c:234};让mutfoo:Foo=Foo{bar:bar,cat:cat};{//foo.cat=&Cat{c:666};让local_cat=&Cat{c:666};foo.cat=&local_cat;println!("{}",foo.cat.c)}println!("{}",foo.cat.c);}答案仍然可以正常工作。再次更改:fnmain(){letbar:&Bar=&Bar{b:123};让猫:&Cat=&Cat{c:234};让mutfoo:Foo=Foo{bar:bar,cat:cat};{//foo.cat=&Cat{c:666};让local_cat=Cat{c:666};foo.cat=&local_cat;println!("{}",foo.cat.c)}println!("{}",foo.cat.c);}编译器终于有了提示,开始提示引用生命周期不长足够的。研究又是为什么尼,再看rustc--explainE0716;直接贴吧:temporariesarenotalwaysdroppedattheendoftheenclosingstatement.在&表达式立即存储到变量的简单情况下,编译器会自动延长临时变量的生命周期,直到封闭块结束。因此,修复原始程序的另一种方法是编写lettmp=&foo()而不是lettmp=foo():fnfoo()->i32{22}fnbar(x:&i32)->&i32{x}letvalue=&foo();letp=bar(value);letq=*p;这里,我们还是借用了foo(),但是由于借用的是直接赋值给一个变量,临时的要等到结束时才会被丢弃封闭块。类似的规则适用于将临时对象存储到元组或结构之类的聚合结构中时://这里创建了两个临时对象,但是//因为它们直接存储到`value`中,//它们直到//封闭block.fnfoo()->i32{22}让值=(&foo(),&foo());也就是说除了let,struct和tuple可以延长临时变量的生命周期避免被释放,所以如果我们使用vec!,还是会报同样的生命周期不够长错误:fnmain(){fnfoo()->i32{22}letv=vec!(&foo(),&foo());println!("{:?}",v);}Structreference成员使用相同的生命周期标识符有问题吗?从文档中了解到,如果struct包含引用类型的字段,它会强制你添加一个生命周期标识符。structFoo<'a>{bar:&'aBar}当然仔细想想也是合理的,因为编译器需要知道Foo实例能存活多久。比如这里,Foo的生命周期一定要长于barShort的引用或者等长,否则就会出现悬空引用。然后当你有两个引用类型的字段时,你会被建议再次这样做:structFoo<'a,'b>{bar:&'aBar,cat:&'bCat}这里使用'a,'b两个生命周期标识,分别标记bar和cat两个引用的生命周期,当然这也很合理,现在编译器知道Foo实例的生命周期必须小于等于最小生命周期酒吧和猫。那么问题来了,如果这里只使用一个生命周期标识'a会怎样:structFoo<'a>{bar:&'aBar,cat:&'aCat}这时,'a会等于replacementbar和cat都是指最小的生命周期,是生命周期的协方差。
