欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 创投人物 > rust语法细节讨论

rust语法细节讨论

2024/10/25 23:33:18 来源:https://blog.csdn.net/tlxamulet/article/details/141558544  浏览:    关键词:rust语法细节讨论

语法

入门中文教程链接:

https://kaisery.github.io/trpl-zh-cn/ch02-00-guessing-game-tutorial.html

字符串的字节与字符

字节是u8,一个字节;字符是unicode编码,占4个字节。

字符串的字节序列用as_bytes()返回,字符序列用chars()返回。

字符串的len()返回的是字节数,而非字符数。字符数用s.chars().count()来获得。

字符串拼接

用+可以,但推荐使用format!宏。例如:

let s = format!("position:{} not recognized", pos)

常量

常量在编译时被求值,它们的值会在使用的地方被内联。

常量初始化时不能调用非const方法,否则报错:

calls in constants are limited to constant functions, tuple structs and tuple variants

原因也是因为const是编译期求值,当然只能调用编译期的const函数了。

const函数

与C++的const方法含义完全不同,C++里const函数的意思是,不修改其成员变量。

但rust里const方法指的是编译时执行的函数 ,所以里面是不能有for循环的。

全局静态变量

当我们用非const函数初始化全局static变量时,也会报与const类似的错误:

calls in statics are limited to constant functions, tuple structs and tuple variants

可见,static与const一样,也是一个编译期的概念,区别仅在于前者会在编译期分配存储空间,而后者不会。

但这种行为就跟C++或java里的全局静态变量区别很大了,后者实际是在运行期初始化的(确切的说,是在程序启动时)。因为实际使用中,很多全局static变量的初始化难以做到编译期就确定、也很难不依赖非const函数。

所以,rust里的全局static变量根本就不是我们想要的。那么,有没有等价替代物呢?

有,用lazy_static!宏:

lazy_static! {static ref RESERVED: Mutex<HashMap<String, TokenKind>> = Mutex::new(HashMap::from([("and".to_string(), TokenKind::AND),("or".to_string(), TokenKind::OR),("div".to_string(), TokenKind::DIV),...]));}

加锁是防止多线程并发访问出错。我们要这样访问:

RESERVED.lock().unwrap()
.get(m.to_lowercase().as_str())
.copied()
.unwrap_or(TokenKind::ID)

数组与Vec

跟C++一样,数组长度编译期指定,所以必须要指定常量长度。

Vec类似于C++的vector,可以自动扩展。而且有vec!宏,写起来跟数组很类似。

hashmap

字面量初始化

hashmap的字面量初始化,使用HashMap::from方法,入参是个数组:

let reserved: HashMap<&str, TokenKind> = HashMap::from([("and", TokenKind::AND),("or", TokenKind::OR),("div", TokenKind::DIV),...]);

不用一个个insert的调用。

带默认值的键查询

get with default的写法:

reserved.get(m.to_lowercase().as_str()).copied().unwrap_or(TokenKind::ID)

get返回的是一个Option<&V>,所以,需要用copied转出Option<V>,最后再用unwrap_or获取键不在时的默认值。

正则表达式

find和capture区别

像python里,提供了几种能力:match从头开始匹配,search从任意位置开始匹配,两者都只匹配一次;findall、finditer则找到所有匹配。且,python并不区分是普通模式还是分组捕获模式(capture),后者仅仅是返回的MatchObject对象支持group(1)、group(2)…方法来获取捕获的分组值。

rust里则无此种能力,find等价于python的search,从任意位置开始匹配,且只匹配一次;find_iter找到所有匹配。但find和find_iter都只对应于普通模式,要做分组捕获,得使用captures方法。

样例如下:

#[test]
fn test_reg_find() {let reg = Regex::new("create\\s+table\\s+(\\w+)");let mo = reg.unwrap().find_at("create table t_a", 0).unwrap();assert_eq!(mo.as_str(), "create table t_a");
}fn test_reg_capture() {let reg = Regex::new("create\\s+table\\s+(\\w+)");let co = reg.unwrap().captures_at("create table t_a", 0).unwrap();assert_eq!(co.get(0).unwrap().as_str(), "create table t_a");assert_eq!(co.get(1).unwrap().as_str(), "t_a");assert_eq!(co.len(), 2);
}

find与find_at

我一开始使用的时候,对find_at的理解错误了,我以为find_at(str, n)等价于find(str[n…]),但实际不是的,看看下面例子:

let s = "  #   ";
let pat = Regex::new("^#").unwrap();
assert_eq!(pat.find(&s[2..]).unwrap().as_str(), "#");
assert_eq!(pat.find_at(s, 2), None);

find_at正如它注释里讲的,它会考虑正则表达式的上下文,这里的上下文就是^,它表示要从整个字符串s的最开始处匹配,即使我们指定了偏移量=2,事实上,它是无效的。

所以,我个人对于提供find_at的动机不太理解,作为API来讲,感觉很容易被误解。

null

rust没有空指针的概念,只能用Option的枚举None。

判断一个对象是否None有两种写法:==或is_none

#[test]
fn test_none() {let tk: Option<TokenKind> = None;assert!(tk == None);assert!(tk.is_none());
}

异常

rust没有异常,只有panic。像unwrap方法一般都会触发panic,panic会导致程序coredump,所以,除了在UT里,一般不建议使用unwrap,而要用match或if let的替代写法,当然,后者写起来比unwrap繁琐多了。

赋值语句

rust里的对象,用引用传递(rust术语叫borrow)没问题,用值传递会导致所有权转移,本质上就是C++里的auto_ptr。所以,看似普通的赋值语句也会像auto_ptr那样带来销毁式拷贝(destruction copy)的效果:

#[test]
fn test_assign() {let r1 = Rectangle {width: 1,height: 2};// value moved herelet r2 = r1;             // value borrowed here after moveprintln!("{}", r1.width);println!("{}", r2.width);
}

对于enum类型,也是这个结论,因为enum本质上也是一个对象。

但对于基本类型(如数字)及字符串字面量(&str)类型,赋值语句并不触发所有权转移,是没有问题的。比如下面代码可以正常执行:

#[test]
fn test_assign() {let i = 1;let j = i;println!("{}", i);println!("{}", j);// 这里一旦写成 let s = "abc".to_String();就会编译报错了let s = "abc";let s1 = s;println!("{}", s);println!("{}", s1);
}

Option::unwrap

注意Option::unwrap的定义:

pub const fn unwrap(self) -> T {match self {Some(val) => val,None => unwrap_failed(),}}

传入的是值self,而不是通常的引用&self。所以,unwarp调用完之后,原本的Option对象就消失了,不能再被访问了。亦即unwrap是一次性的调用方法。

闭包与函数指针

闭包就是匿名函数,可以捕获上下文。类似于其它语言的lambda。它有三个trait(FnFnMutFnOnce)。

函数指针(fn,注意f小写)实现了所有三个闭包 trait(FnFnMutFnOnce),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。

亦即,函数指针就是闭包。

但,闭包不一定是函数指针。闭包只有在不引用upvalue的时候,才可以被自动转型为函数指针,否则会报错:

closures can only be coerced to `fn` types if they do not capture any variables

注意:fn是类型,Fn是trait,fn可以用作泛型参数,而Fn则必须加上dyn关键字才可用作泛型参数

闭包作为Vec元素

由前可知,Fn是trait,所以Vec类型参数里不能直接写Fn,但可以写fn。此时只要传入的闭包元素不引用upvalue,编译器会自动将fn强转Fn的。但若引用了upvalue,指定Vec类型参数为Fn,像这样:

let v: Vec<dyn Fn(&str) -> usize> = vec![|s| s.len(), |s| s.len()+x];

就会报错:

the size for values of type `dyn for<'a> Fn(&'a str) -> usize` cannot be known at compilation time

此时只能使用Box来解决trait object作为Vec类型参数无法计算size的问题,这么写即可:

let v: Vec<Box<dyn Fn(&str) -> usize> > = vec![Box::new(|s| s.len()), Box::new(|s| s.len()+x)];
for item in v {println!("{}", item("abc"));
}

这里特别说明一点:声明时必须明确指定Box<dyn Fn(&str) -> usize>类型作为Vec元素,否则依赖rust的自动类型推断,又会报错:

let v: Vec<_> = vec![Box::new(|s:&str| s.len()), Box::new(|s:&str| s.len()+x)];

这里未强制指定Vec元素类型,rust会报错:

expected closure, found a different closure

因为rust认为

|s:&str| s.len()
|s:&str| s.len()+x)

是两种类型的闭包,尽管两者的入参和返回值是一样的。

或者这样写也是可以的,就是难看点:

let v: Vec<_> = vec![Box::new(|s:&str| s.len()) as Box<dyn Fn(&str) -> usize>, Box::new(|s:&str| s.len()+x)];

这里把第一个闭包强转为Box<dyn Fn(&str) -> usize>,给rust类型推断一个指引,让它知道Vec的元素到底是啥,后面的闭包就不用再强转了。

struct构造

struct的构造器写起来有点麻烦,正常是要带字段名。不过,如果入参同字段名相同,也是可以省略掉字段名的。

我们一般会封装静态构造方法来简化struct的构造。不过rust不支持函数重载,所以不同参数个数的静态构造器得起不同的名字,这点比较麻烦(或者用builder模式?)。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com