企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持知识库和私有化部署方案 广告
在c中,表达式和声明之间有很大的区别,表达式会产生值,声明不会。 rust被叫做表达式语言,这意味着它遵循旧传统,可以追随到lisp,在lisp里,表达式可以做任何事。 在c中,if和switch都是声明,它们不产生值,它们不能用到表达式中间,在rust中if和match可以产生值。一个if表达式可以用于初始化一个变量。一个match表达式可以作为参数传递给函数或者宏。这解释了rust为什么没有c的问号表达式,在c中它是if声明相同逻辑的表达式,在rust中,问号表达式就多余了,if表达式就可以直接用了。 在c中,大部分控制流是声明,在rust中,它们都是表达式。 块也是表达式,块产生一个值,并且能用到任何需要值的地方。块的值是最后一个表达式的值。记住在最后一个表达式后面没有分号,大多数rust代码行后面跟着分号或者大括号。假如块的最后一个表达式也加了分号,那它产生的值是空元组。如果最后一个表达式不加分号,那么块产生的值就是最后一个表达式的值。 let声明后面的分号是总是必须的。 块里面产生值的特性是整洁自然的,唯一的缺点就是如果忘记在最后一个表达式末尾加分号可能导致奇怪的报错。在if语句中如果省略else部分,那么else部分默认是个空块,产生的值是(),如果if部分最后的表达式省略了分号,返回的不是()就和else部分返回的值类型不一致,导致编译器报错。当你遇到expected type ()报错信息的时候,检查是不是省略了else部分的if语句同时还漏掉了最后一个表达式的分号。 空白声明在块中是允许的,空白声明由单独的分号组成。 除了表达式和分号,块还可以包含任意数量的声明declaration。最常见的就是let声明。let声明里类型和初始值是可选的,分号是必须的。let声明的时候不初始化,在之后的代码里用赋值语句初始化是可以的,有时是必要的,比如在if语句中给变量初始化,两个分支只执行其中一个,所以变量可以不声明成可变变量。 在初始化之前使用变量是非法的,这和变量发生移动再使用很相似。你偶尔可以看到重新声明一个已存在的变量,let声明创建一个相同名字的新的不同类型的变量,这是合法的。 块里面还可以包含item声明,一个item声明可以是出现在全局代码里的模块,比如fn,struct,use等等。 任何块都可以包含fn函数定义,并且定义是提前的,可以先调用再写声明。当一个函数在块里面定义,那么它的作用域就只在块里,在整个块都可以调用,但是不能访问块里的局部变量或者函数的参数,块里面甚至可以包含一个完整的模块。这对写宏很有用。 if语句的测条件表达式必须是严格的bool类型,rust不会做任何隐式转换。条件的表达式两边的圆括号不是必须的,实际上如果你加了圆括号,rust编译器会给你一个警告提示。大括号却是必须的,只有一句代码也不能省略。else if和else块是可选的,如果省略了else块,那等同于一个空块。 match表达式和c里面的switch语句很相似,但是更具有弹性,match语句的通配符\_分支类似switch语句的default分支。编译器可以用一个跳转表优化match表达式,类似c的switch语句。当每个分支都生成一个常量值的话,编译器会优化match表达式成一个数组访问。 match分支还可以用模式匹配,模式匹配是一个mini语言提供更强大的功能。 如果match分支的表达式是一个块,那么分支后面的逗号可以省略。如果某个分支匹配到了,那么后面的分支都不会执行,match表达式执行完成。至少一个分支要匹配到,rust禁止match分支没有包含所有的情况。if和match表达式的所有分支必须产生相同类型的值。 if语句的条件表达式可以是个let加模式匹配的语法,匹配到执行第一个分支,匹配不到执行第二个分支,这在从Option和Result类型里取值的时候很方便。 if let表达式用match表达式可以完美替换,可以认为是match表达式的一种使用场景的简写而已。 rust中循环表达式不产生有用的值,只产生空元组(),while循环表现和c相同,除了条件表达式必须是bool类型。while let循环和if let相似,在每个循环的开始用表达式赋值给模式,如果模式匹配,则执行循环体,如果不匹配就跳出循环。loop是永久循环,它执行反复执行循环体,直到遇到break,return或者线程恐慌。for循环迭代一个集合,支持许多集合类型。 ..操作符产生一个区间Range,一个简单的结构体,包含2个属性start和end,0..20和std::ops::Range{start:0,end:20}相同。区间Range可以用于循环,因为Range是可迭代类型。它实现了std::iter::IntoIterator特征,标准的集合都是可迭代的,比如数组和切片。因为移动语义,for循环会消费掉整个集合,可以把引用给for循环迭代以防止被消费掉,这样迭代出来的项也是引用。用for迭代可变引用,迭代出来的项是可变引用,集合被消费??? break表达式跳出循环,在rust中,break只可以出现在循环中,在match表达式中没有必要用break。到底可不可以用?? continue表达式提前结束当轮循环进入下一轮,在for循环中如果没有下一个值就退出循环,while循环中会重新检查条件,如果条件为false就退出循环。 循环可以标记一个生命周期,然后break跟随一个生命周期可以跳出到指定循环,continue也类似。 return表达式退出当前函数,并返回携带的值,return没有返回值就是返回()。类似break表达式,return也可以提前终止程序。 问号跟在表达式后面是取Result类型数据的简写,如果Result有值,就取出该值,如果没有就提前return整个函数,并返回错误。 rust并不检查循环条件,它会默认所有条件都既可能为true也可能为false,即使条件里面就是true直接量。 if表达式的所有分支必须产生相同的类型值。不会正常返回的表达式用特殊类型!表示,函数std::process::exit()返回值的类型为!。如果你指定函数返回!,而函数又能正常返回,编译器会报错。 rust在引用和值之间区分严格,假如你传递一个&i32给一个期望i32值的函数,是错误的。但是点操作符会放松这个限制,点操作符会自动解引用或者在需要的时候生成一个引用。 调用静态方法用::连接,比如Vec::new(),调用非静态方法用点连接比如my\_vec.len(),类似其它面向对象语言,非静态方法在值上调用,静态方法在类型上调用。 rust的一个奇怪的语法是泛型参数的写法,不是在类型后面加而是在类型后面加::,直接加中的<被认为是小于符号。泛型参数在编译器可以推断的时候经常可以省略。 结构体成员的访问是熟悉的语法,用点号,元组也可以用点号加数字访问成员。 对成员的访问也可以自动解引用,就像调用方法那样。对集合元素的访问用方括号,方括号也可以自动解引用。这些表达式是左值,可以出现在赋值表达式的左边。 从数组或者向量生成切片很简单,直接变量引用(变量名)加一个range表达式就可以了,range表达式的..操作符两边都可以省略,省略的就认为是边界值,左边省略是0,右边省略是数组或者向量的长度,range区间是半开区间,左边包含右边不包含。 星号\*用于手动解引用,由于点号可以自动解引用,所以星号只有在需要读或写引用的整个值的时候有必要。 整型溢出是被监控的,在调试构建中会触发恐慌,标准库提供了wapping\_add可以用于不检查的加法。 除法的除数是0即使在发行构建里面也会触发恐慌,整数有个方法a.checked\_div(b),返回一个Option,假如除数是0,则返回None,这个方法永远不会恐慌。 负号-用在除无符号整型外的所有数字类型上,没有正号+操作符。 像c那样,%操作符求模,符号与被除数相同???求模也可以用于浮点型。 rust继承了c的位操作符&,|,^,>,但是用!替代~表示位非。 不能用!n表示n为0。 位移用于有符号整型会保留符号,遇到无符号整型会补0,由于rust有无符号整型,所以没有java里的>>>。 和c不同,位操作符比比较操作符有更高的优先级。 比较操作符和c一样,不过两边的值必须是相同的类型。 逻辑操作符也和c一样,连接的必须是严格的bool类型。 赋值操作符=用于给可变变量或者它们的成员赋值,赋值操作对于非拷贝类型会执行移动语义。 +=,-=,\*=,/=是支持的,但是自增++和自减--不支持。 类型转换需要用as关键字显式转换。数字可以转换成其它内建的数字类型,转换成更窄的类型会截断,有符号整型转成更宽的类型会保留符号。转换一个大的浮点成一个小的整型会导致不确定的行为,甚至会使程序崩溃,这是编译器的一个bug。 bool,char或者enum可以转换成整型。 其他方向的转换不被允许,比如把u16转换成字符型不允许,不是所有的u16类型整数都对应的有unicode字符,有标准库的std::char::from\_u32()用于安全的转换,它返回Option类型,如果能转才有值,否则返回None。 作为一个例外,u8类型是可以转换成char类型的,因为u8类型里所有的数字都对应得有字符。 有些引用类型之间的转换不用强制类型转换,比如把可变引用转成不可变引用。 &String自动转成&str,&Vec自动转成&\[i32\],&Box自动转成&T。这叫强制解引用,因为它们实现了Deref特征,它使一些智能指针类型更像普通类型,用户自定义类型也可以实现Deref特征。 rust有闭包,闭包通常有参数列表,用两根竖线包着,rust会推断闭包的参数类型和返回值类型,你也可以显式指定就像函数那样。如果你展示指定了一个返回值类型,那么闭包体必须是一个块,调用闭包和调用函数一样的语法。 比较操作符,赋值操作符和区间操作符..不能被链起来。