开始
今天开始,为了切入webassembly,入坑Rust;
那么问题来了:为什么现在就要切入webassembly?为什么选择Rust,而不选择c/c++,go等语言?
正如同大家所知道的,webassembly还不是十分成熟,离应用到生产环境还是有一段不少的距离,那么有必要那么急着把精力投入到这个领域上面去吗(Vue3.0,React源码还想去看)但是个人看法现在正是时候了。
第一,webassembly大势所趋,有很多人和公司都跃跃欲试,特别是游戏领域;
第二,webassembly对于大部分前端(包括我)来说必然是要学习一门静态语言(如果是c/c++大牛可以忽略)需要一定时间学习积淀。
那么另外一个问题为什么不选择c/c++或者go等语言尼;c/c++几十年过去了生态依然很强,而且学习的资料也很多,应用广阔,但是有一个问题很重要就是历史包袱很重,个人无法在短时间内能够熟练运用;而go,java等语言,学习成本不算高,应用也很广,其实用来切入webassembly真的无往而不利,只是目前有一个问题就是要带自己的gc,在webassembly还没到完全体的时候,始终是一道坎,或许3到5年后这个问题也不成问题了。
那么Rust有什么优势尼:
- 年轻,历史包袱比较小
- 设计也比较严谨
- 工具链也比较人性化,cargo跟npm使用体验上说还是挺相似的
- 性能也不错,可以在后端搭配node,做一些性能优化点
但是学习成本还是挺高,介于c++和go语言之间(个人认为)。以上综合一下rust算是以一点微弱优势胜出,其实嘛,rust是火狐主推的语言,从长远看还是会无限贴近web的开发者,这也是它潜在的一个优势。
Rust相关中文资料
Rust 程序设计语言(第二版) 简体中文版
通过例子学Rust
Rust死灵书
Rust宏小册 中文版
以后有更多中文资料会持续更新
Rust迷思
temporary value is freed at the end of this statement
示例代码如下:
struct Cat {
pub c: i32
}
struct Bar {
pub b: i32
}
struct Foo<'a> {
bar: &'a Bar,
cat: &'a Cat
}
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let foo: &mut Foo;
{
foo = &mut Foo{bar: bar, cat: cat}; //error
println!("{}", foo.cat.c);
}
}
直接执行就会报这样一个错误:
|
17 | foo = &mut Foo{bar: bar, cat: cat};
| ^^^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
| |
| creates a temporary which is freed while still in use
18 | println!("{}", foo.cat.c);
| --------- borrow later used here
|
= note: consider using a `let` binding to create a longer lived value
但是我把代码改变一下:
{
let foo = &mut Foo{bar: bar, cat: cat};
println!("{}", foo.cat.a);
}
这样是正常执行的;
但是第一种为什么跑不通尼,根据编译器提示,执行rustc --explain E0716,看一下编译器给的提示(果然rust是编译器教你写代码);
大概总结一下:
foo = &mut Foo{bar: bar, cat: cat};
大致等同于:
foo = {
//创建了一个tmp变量,拥有Foo实例的所有权
let mut tmp = Foo{bar: bar, cat: cat};
//返回tmp的引用
&mut tmp
}
tmp的生命周期明显只在花括号范围内,脱离之后就要释放,所以返回的是悬垂引用。
而使用let的时候,对于临时变量的引用,编译器会自动扩展临时变量的生命周期的。
又另外一种情况,把main函数的代码改一下:
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let mut foo: Foo = Foo {bar: bar, cat: cat};
{
foo.cat = &Cat{c: 666};
println!("{}", foo.cat.c)
}
println!("{}", foo.cat.c);
}
这代码会正确运行吗,foo.cat引用了里面的一个临时变量;
答案是正确会运行的。
再改一下:
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let mut foo: Foo = Foo {bar: bar, cat: cat};
{
//foo.cat = &Cat{c: 666};
let local_cat = &Cat{c: 666};
foo.cat = &local_cat;
println!("{}", foo.cat.c)
}
println!("{}", foo.cat.c);
}
答案还是会正确运行的。再改一下:
fn main() {
let bar: &Bar = &Bar{ b: 123 };
let cat: &Cat = &Cat{ c: 234 };
let mut foo: Foo = Foo {bar: bar, cat: cat};
{
//foo.cat = &Cat{c: 666};
let local_cat = Cat{c: 666};
foo.cat = &local_cat;
println!("{}", foo.cat.c)
}
println!("{}", foo.cat.c);
}
编译器终于有提示了,开始提示引用生命周期不够长了。究竟又是为什么尼,再看看rustc --explain E0716;直接贴吧:
Temporaries are not always dropped at the end of the enclosing
statement. In simple cases where the&
expression is immediately
stored into a variable, the compiler will automatically extend
the lifetime of the temporary until the end of the enclosing
block. Therefore, an alternative way to fix the original
program is to writelet tmp = &foo()
and notlet tmp = foo()
:
fn foo() -> i32 { 22 }
fn bar(x: &i32) -> &i32 { x }
let value = &foo();
let p = bar(value);
let q = *p;
Here, we are still borrowingfoo()
, but as the borrow is assigned
directly into a variable, the temporary will not be dropped until
the end of the enclosing block. Similar rules apply when temporaries
are stored into aggregate structures like a tuple or struct:
// Here, two temporaries are created, but
// as they are stored directly into `value`,
// they are not dropped until the end of the
// enclosing block.
fn foo() -> i32 { 22 }
let value = (&foo(), &foo());
也就是说除了let,struct和tuple都可以扩展临时变量的生命周期以免被释放。
所以我们如果用vec!的话还是会报同样的生命周期不够长的错误:
fn main() {
fn foo() -> i32 { 22 }
let v = vec!(&foo(), &foo());
println!("{:?}", v);
}
Struct的引用成员使用相同的生命周期标识符会有问题吗?
从文档学习的时候,如果struct含有引用类型的字段,都会强制要求你添加生命周期标识符。
struct Foo<'a> {
bar: &'a Bar
}
当然仔细想想也是很合理,因为要让编译器知道Foo实例最多能存活到什么时候,例如这里,Foo的生命周期肯定要比bar的引用要短或者一样长,否则就会出现悬垂引用了。那么当拥有两个引用类型的字段的时候,又会建议你这么做:
struct Foo<'a, 'b> {
bar: &'a Bar,
cat: &'b Cat
}
这里使用了‘a,’b两个生命周期标识符,分别标记bar和cat这两个引用的生命周期,当然这也是很合理,现在编译器知道Foo实例的生命周期肯定小于等于bar和cat两者中最小的生命周期了。
那么问题来了,如果这里只使用一个生命周期标识符‘a又会怎么样尼:
struct Foo<'a> {
bar: &'a Bar,
cat: &'a Cat
}
这时候‘a会等于替换成bar和cat两者引用最小的生命周期;