rust语法细节讨论

2024-08-26 21:36
文章标签 rust 语法 细节 讨论

本文主要是介绍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模式?)。

这篇关于rust语法细节讨论的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1109743

相关文章

【Rust练习】12.枚举

练习题来自:https://practice-zh.course.rs/compound-types/enum.html 1 // 修复错误enum Number {Zero,One,Two,}enum Number1 {Zero = 0,One,Two,}// C语言风格的枚举定义enum Number2 {Zero = 0.0,One = 1.0,Two = 2.0,}fn m

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

C++语法知识点合集:11.模板

文章目录 一、非类型模板参数1.非类型模板参数的基本形式2.指针作为非类型模板参数3.引用作为非类型模板参数4.非类型模板参数的限制和陷阱:5.几个问题 二、模板的特化1.概念2.函数模板特化3.类模板特化(1)全特化(2)偏特化(3)类模板特化应用示例 三、模板分离编译1.概念2.模板的分离编译 模版总结 一、非类型模板参数 模板参数分类类型形参与非类型形参 非类型模板

Java基础回顾系列-第一天-基本语法

基本语法 Java基础回顾系列-第一天-基本语法基础常识人机交互方式常用的DOS命令什么是计算机语言(编程语言) Java语言简介Java程序运行机制Java虚拟机(Java Virtual Machine)垃圾收集机制(Garbage Collection) Java语言的特点面向对象健壮性跨平台性 编写第一个Java程序什么是JDK, JRE下载及安装 JDK配置环境变量 pathHe

第二十四章 rust中的运算符重载

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust中的运算符重载 目录 注意一、前言二、基本使用三、常用运算符四、通用约束 一、前言 C/C++中有运算符重载这一概念,它的目的是让即使含不相干的内容也能通过我们自定义的方法进行运算符操作运算。 比如字符串本身是不能相加的,但由于C++中的String重载了运算符+,所以我们就可以将两个字符串进行相加、但实际

Hibernate框架中,使用JDBC语法

/*** 调用存储过程* * @param PRONAME* @return*/public CallableStatement citePro(final String PRONAME){Session session = getCurrentSession();CallableStatement pro = session.doReturningWork(new ReturningWork<C

ORACLE语法-包(package)、存储过程(procedure)、游标(cursor)以及java对Result结果集的处理

陈科肇 示例: 包规范 CREATE OR REPLACE PACKAGE PACK_WMS_YX IS-- Author : CKZ-- Created : 2015/8/28 9:52:29-- Purpose : 同步数据-- Public type declarations,游标 退休订单TYPE retCursor IS REF CURSOR;-- RETURN vi_co_co

vscode中使用go环境配置细节

1、在docker容器中下载了go的sdk 2、在/etc/profile.d/go.sh里填入如下内容: #!/bin/bashexport GOROOT=/home/ud_dev/goexport PATH=$GOROOT/bin:$PATH  3、设置go env go env -w GOPROXY=https://goproxy.cn,directgo env -w GO

ElasticSearch的DSL查询⑤(ES数据聚合、DSL语法数据聚合、RestClient数据聚合)

目录 一、数据聚合 1.1 DSL实现聚合 1.1.1 Bucket聚合  1.1.2 带条件聚合 1.1.3 Metric聚合 1.1.4 总结 2.1 RestClient实现聚合 2.1.1 Bucket聚合 2.1.2 带条件聚合 2.2.3 Metric聚合 一、数据聚合 聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。例如:

react笔记 8-16 JSX语法 定义数据 数据绑定

1、jsx语法 和vue一样  只能有一个根标签 一行代码写法 return <div>hello world</div> 多行代码返回必须加括号 return (<div><div>hello world</div><div>aaaaaaa</div></div>) 2、定义数据 数据绑定 constructor(){super()this.state={na