Jogger跑鞋零撸项目系统开发/方案详细/规则玩法/源码案例/功能说明
Development status of multi blockchain smart contract compatibility technology
At present, multi blockchain smart contract compatibility technology mainly includes two ways: one is to implement cross chain smart contracts, which is to apply smart contracts to cross chain scenarios; Another approach is to use converters to convert smart contracts from one programming language to another, thereby achieving compatibility between different blockchains.
Cross chain smart contract technology
Cross chain smart contract technology is currently a popular multi blockchain smart contract compatibility technology. It achieves cross chain data transmission and smart contract execution by establishing bridges between blockchains. Cross chain smart contract technology requires the establishment of a connection channel between multiple blockchains, and the communication and state exchange of smart contracts on this channel.
At present, cross chain smart contract technology has been widely applied among various blockchains. For example, Cosmos adopts IBC technology, which enables Cosmos to establish mutual trust relationships with other blockchain networks, enabling cross chain transactions and communication.
However, there are also some issues and challenges with cross chain smart contract technology. Firstly, security is a major challenge that cross chain smart contract technology needs to address. The designer of cross chain smart contracts needs to consider the different characteristics and risks that may exist between multiple blockchains, as well as whether there are vulnerabilities and security risks in the interaction between these blockchains. Secondly, in the implementation of cross chain technology, it is necessary to establish a large number of connections and communication, which can lead to low execution speed of cross chain smart contracts and affect the user experience. Therefore, in the future development of cross chain smart contract technology, it is necessary to further address these issues to meet the needs of practical applications.
Smart contract conversion technology
Smart contract conversion technology is another multi blockchain smart contract compatibility technology. It achieves cross blockchain smart contract compatibility by adopting a unified contract programming language and specification during contract writing, and converting the written code into contract code supported by the target blockchain through a converter.
Taking X-Chain as an example, X-Chain adopts a WebAssembly based smart contract virtual machine and supports multiple programming languages such as Rust and C++. Smart contracts deployed on X-Chain can be converted into programming languages and specifications supported by other blockchains through converters, thereby achieving compatibility between smart contracts across different blockchains.
Similarly, smart contract conversion technology also faces many technical challenges. Firstly, due to the different specifications and programming languages of smart contracts between different blockchains, it is necessary to convert them for different target blockchains and ensure that the converted smart contracts can execute normally on the target blockchain. Secondly, in terms of security, performance, reliability, and other aspects, smart contract conversion technology also needs to be further improved and optimized.
interface uniSwap{
//1、用指定的代币交唤代币
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[]calldata path,
address to,
uint deadline
)external returns(uint[]memory amounts);
//2、用代币交唤指定的代币
function swapTokensForExactTokens(
uint amountOut,
uint amountInMax,
address[]calldata path,
address to,
uint deadline
)external returns(uint[]memory amounts);
//3、用指定的ETH币交唤代币
function swapExactETHForTokens(uint amountOutMin,address[]calldata path,address to,uint deadline)
external
payable
returns(uint[]memory amounts);
//4、用代币交换指定的ETH币
function swapTokensForExactETH(uint amountOut,uint amountInMax,address[]calldata path,address to,uint deadline)
external
returns(uint[]memory amounts);
//5、用指定的代币交换ETH币
function swapExactTokensForETH(uint amountIn,uint amountOutMin,address[]calldata path,address to,uint deadline)
external
returns(uint[]memory amounts);
//6、用ETH币交换指定的代币
function swapETHForExactTokens(uint amountOut,address[]calldata path,address to,uint deadline)
external
payable
returns(uint[]memory amounts);
//1、添加流动性
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
)external returns(uint amountA,uint amountB,uint liquidity);
//2、添加ETH币流动性
function addLiquidityETH(
address token,
uint amountTokenDesired,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
)external payable returns(uint amountToken,uint amountETH,uint liquidity);
//3、移除流动性
function removeLiquidity(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
)external returns(uint amountA,uint amountB);
//4、移除ETH币流动性
function removeLiquidityETH(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline
)external returns(uint amountToken,uint amountETH);
//5、凭许可证消除流动性
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint liquidity,
uint amountAMin,
uint amountBMin,
address to,
uint deadline,
bool approveMax,uint8 v,bytes32 r,bytes32 s
)external returns(uint amountA,uint amountB);
//6、凭许可证消除ETH流动性
function removeLiquidityETHWithPermit(
address token,
uint liquidity,
uint amountTokenMin,
uint amountETHMin,
address to,
uint deadline,
bool approveMax,uint8 v,bytes32 r,bytes32 s
)external returns(uint amountToken,uint amountETH);
}
contract MyUni{
//using TransferHelper for*;
//合约接受转币功能
receive()external payable{
}
Rust 笔记:Rust 语言中的 结构体
Rust 笔记Rust 语言中的 结构体作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130876493【介绍】:本文先介绍 Rust 语言中的 结构体 的基本用法,然后重点介绍了在 Rust 语言中通过结构体实现面向对象编程的思想、方法并给出了相当多的代码示范。上一节:《 Rust 语言中的 所有权 》 | 下一节:《 Rust 语言中的枚举 》目 录1. 结构体入门1.1 什么是结构体1.2 结构体的定义和使用1.3 为什么使用结构体1.4 入门示例:定义一个简单的结构体2. 结构体中的成员2.1 成员变量2.2 成员方法2.3 进阶示例:定义包含变量和方法的结构体3. 结构体的实例化3.1 创建结构体实例3.2 结构体实例的初始化3.3 使用示例:实例化一个结构体并初始化4. 结构体的实现4.1 为结构体实现方法4.2 为结构体实现 trait4.3 示例:为结构体实现方法和 trait5. 结构体与面向对象5.1 面向对象的特点:抽象、封装、继承、多态5.2 结构体的继承5.2.1 tuple 结构体5.2.2 结构体的嵌套5.2.3 实战示例:结构体的继承和嵌套5.3 使用结构体描述对象5.4 与基于类描述对象的对比5.4.1 封装性5.4.2 继承性5.4.3 多态性5.4.4 所有权系统6. 总结1. 结构体入门1.1 什么是结构体结构体是一种自定义的数据类型,它允许我们将不同类型的数据组合在一起,形成一个新的类型。1.2 结构体的定义和使用在Rust中,可以使用struct关键字来定义结构体,并在结构体内部定义其字段。struct Person {
name: String,
age: u32,
}上面的代码定义了一个名为Person的结构体,它有两个字段:name和age,分别对应字符串类型和无符号整数类型。要创建结构体的实例,可以使用以下方式:let person1 = Person {
name: String::from("Alice"),
age: 25,
};在上面的示例中,我们创建了一个名为person1的Person结构体实例,并初始化了其字段的值。1.3 为什么使用结构体使用结构体可以将相关的数据组合在一起,形成一个有组织的数据结构。结构体可以提供更好的代码组织和可读性,同时还可以为结构体定义方法和实现特定的行为。1.4 入门示例:定义一个简单的结构体struct Point {
x: f32,
y: f32,
}上述代码定义了一个名为Point的结构体,它有两个字段:x和y,类型为f32,即单精度浮点数。我们可以创建该结构体的实例并访问其字段:let origin = Point { x: 0.0, y: 0.0 };
println!("Origin: ({}, {})", origin.x, origin.y);在上面的示例中,我们创建了一个名为origin的Point结构体实例,并通过.操作符访问了其字段的值。通过结构体,我们可以方便地组合多个相关字段,形成自定义的数据类型,并使用这些类型的实例进行操作和访问。这样可以提高代码的可读性和可维护性,同时还能更好地组织数据和行为。2. 结构体中的成员2.1 成员变量结构体中的成员变量用于存储不同类型的数据,例如整数、浮点数、字符串等。成员变量可以通过结构体实例的字段名来访问和操作。struct Rectangle {
width: u32,
height: u32,
}上述代码定义了一个名为Rectangle的结构体,它有两个成员变量:width和height,类型为u32,即无符号32位整数。let rect = Rectangle {
width: 10,
height: 20,
};在上面的示例中,我们创建了一个名为rect的Rectangle结构体实例,并初始化了其width和height成员变量的值。println!("Width: {}", rect.width);
println!("Height: {}", rect.height);通过 . 操作符,我们可以访问结构体实例的成员变量并获取其值。2.2 成员方法结构体可以定义成员方法,也称为关联函数。成员方法用于在结构体上执行特定的操作,可以访问结构体的成员变量和其他方法。impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}在上述代码中,我们通过impl块为Rectangle结构体实现了一个名为area的方法。该方法用于计算矩形的面积,它获取一个&self参数(即结构体实例的借用),并返回一个u32类型的值。let rect = Rectangle {
width: 10,
height: 20,
};
println!("Area: {}", rect.area());在上面的示例中,我们创建了一个名为rect的Rectangle结构体实例,并通过调用area方法计算了其面积。通过.操作符,我们可以在结构体实例上调用方法并获取返回值。2.3 进阶示例:定义包含变量和方法的结构体struct Circle {
radius: f32,
}
impl Circle {
fn new(radius: f32) -> Circle {
Circle { radius }
}
fn area(&self) -> f32 {
3.14 * self.radius * self.radius
}
}fn main() {
let circle = Circle::new(5.0);
println!("Area: {}", circle.area());
}上述示例中,我们定义了一个名为 Circle 的结构体,它有一个成员变量 radius 表示半径。我们还为Circle结构体实现了一个关联函数 new 用于创建实例,并定义了一个成员方法 area 用于计算圆的面积。在 main 函数中,我们使用 Circle::new 关联函数创建了一个 Circle 结构体实例,并通过调用 area 方法计算了其面积。通过成员变量和成员方法,结构体提供了一种方便的方式来存储和操作数据。结构体的成员变量可以保存不同类型的数据,成员方法可以对结构体进行特定的操作,使得代码更加清晰和模块化。3. 结构体的实例化3.1 创建结构体实例要创建结构体的实例,可以使用结构体名和初始化成员变量的值。struct Car {
make: String,
model: String,
year: u32,
}上述代码定义了一个名为Car的结构体,它有三个成员变量:make、model和year,分别对应字符串类型和无符号32位整数类型。let car = Car {
make: String::from("Toyota"),
model: String::from("Camry"),
year: 2021,
};在上面的示例中,我们创建了一个名为car的Car结构体实例,并初始化了其成员变量的值。通过在花括号中提供成员变量名和对应的值,我们可以对结构体进行初始化。3.2 结构体实例的初始化我们可以选择只对结构体的部分成员变量进行初始化。在初始化时,未指定的成员变量将使用默认值。let car = Car {
make: String::from("Toyota"),
model: String::from("Camry"),
..Default::default()
};在上面的示例中,我们使用了Default trait 中的 default 方法来初始化剩余的成员变量为默认值。3.3 使用示例:实例化一个结构体并初始化struct Person {
name: String,
age: u32,
city: String,
}
impl Person {
fn new(name: String, age: u32, city: String) -> Person {
Person {
name,
age,
city,
}
}
}fn main() {
let person1 = Person::new(String::from("Alice"), 25, String::from("New York"));
let person2 = Person {
name: String::from("Bob"),
..person1
};
println!("Person 1: {}, {}, {}", person1.name, person1.age, person1.city);
println!("Person 2: {}, {}, {}", person2.name, person2.age, person2.city);
}在上述示例中,我们定义了一个名为Person的结构体,它有三个成员变量:name、age和city。我们为Person结构体实现了一个关联函数new,用于创建实例并初始化其成员变量。在main函数中,我们使用Person::new关联函数创建了一个名为person1的Person结构体实例。然后,我们使用结构体初始化语法通过person1的值来初始化person2,其中只指定了name字段,其他字段使用person1的对应值。通过结构体的实例化和初始化,我们可以根据需要创建具有不同属性的结构体实例,并灵活地操作和访问其成员变量。这为我们提供了更多控制和定制化的能力。4. 结构体的实现4.1 为结构体实现方法在Rust中,我们可以为结构体实现方法,使得结构体实例能够执行特定的行为。struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn is_square(&self) -> bool {
self.width == self.height
}
}上述代码定义了一个名为Rectangle的结构体,并为其实现了两个方法:area和is_square。area方法计算矩形的面积,is_square方法判断矩形是否为正方形。let rect = Rectangle {
width: 10,
height: 20,
};
println!("Area: {}", rect.area());
println!("Is Square: {}", rect.is_square());在上面的示例中,我们创建了一个名为rect的Rectangle结构体实例,并通过调用area和is_square方法来获取矩形的面积和判断是否为正方形。通过为结构体实现方法,我们可以将特定的行为与结构体关联起来,使得结构体实例能够直接调用方法来执行相应的操作。4.2 为结构体实现 trait除了可以为结构体实现自定义的方法外,我们还可以为结构体实现特定的 trait,从而赋予结构体更多的功能和行为。struct Circle {
radius: f64,
}
trait Shape {
fn area(&self) -> f64;
}
impl Shape for Circle {
fn area(&self) -> f64 {
3.14 * self.radius * self.radius
}
}上述代码定义了一个名为Circle的结构体,并为其实现了Shape trait。Shape trait 包含一个area方法,用于计算形状的面积。let circle = Circle { radius: 5.0 };
println!("Area: {}", circle.area());在上面的示例中,我们创建了一个名为circle的Circle结构体实例,并通过调用area方法来获取圆形的面积。通过为结构体实现 trait,我们可以将特定的行为和功能抽象出来,并将其应用于不同的结构体上。这样可以实现代码的重用和更好的模块化。4.3 示例:为结构体实现方法和 traitstruct Square {
side_length: u32,
}
trait Shape {
fn area(&self) -> u32;
fn perimeter(&self) -> u32;
}
impl Shape for Square {
fn area(&self) -> u32 {
self.side_length * self.side_length
}
fn perimeter(&self) -> u32 {
4 * self.side_length
}
}
impl Square {
fn is_square(&self) -> bool {
self.side_length > 0 && self.side_length == self.perimeter() / 4
}
}fn main() {
let square = Square { side_length: 5 };
println!("Area: {}", square.area());
println!("Perimeter: {}", square.perimeter());
println!("Is Square: {}", square.is_square());
}在上述示例中,我们定义了一个名为Square的结构体,并为其实现了Shape trait 和 is_square 方法。Shape trait 包含了计算面积和周长的方法。在main函数中,我们创建了一个名为square的Square结构体实例,并通过调用area、perimeter和is_square方法来获取正方形的面积、周长以及判断是否为正方形。通过结构体的方法和 trait 的实现,我们可以扩展结构体的功能,使其具备更多的行为和特性,同时也提高了代码的可读性和可维护性。5. 结构体与面向对象5.1 面向对象的特点:抽象、封装、继承、多态面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,具有以下特点:抽象(Abstraction):通过将现实世界的对象抽象成类和对象的概念,从而将复杂的问题简化为更易于理解和处理的模块。封装(Encapsulation):将数据和操作封装在对象中,对象对外部隐藏了内部实现细节,只提供有限的接口与外界进行交互。继承(Inheritance):通过定义父类和子类之间的关系,子类可以继承父类的属性和方法,从而实现代码的重用和扩展。多态(Polymorphism):同一种操作可以根据不同对象的类型执行不同的行为,提高代码的灵活性和可扩展性。5.2 结构体的继承在Rust中,没有直接的结构体继承机制,但可以通过其他方式实现类似的功能。5.2.1 tuple 结构体Tuple 结构体可以被看作是一组没有具名字段的结构体,可以用于表示一组相关的值。struct Person(String, u32);
fn main() {
let person = Person(String::from("Alice"), 25);
println!("Name: {}", person.0);
println!("Age: {}", person.1);
}在上述示例中,我们定义了一个名为Person的 tuple 结构体,它包含了一个字符串类型和一个无符号32位整数类型。通过索引访问元组的元素,可以获得相应的值。5.2.2 结构体的嵌套结构体可以相互嵌套,形成层级关系,从而实现类似继承的效果。struct Person {
name: String,
age: u32,
}
struct Employee {
person: Person,
employee_id: u32,
}
fn main() {
let person = Person {
name: String::from("Alice"),
age: 25,
};
let employee = Employee {
person,
employee_id: 12345,
};
println!("Name: {}", employee.person.name);
println!("Age: {}", employee.person.age);
println!("Employee ID: {}", employee.employee_id);
}在上面的示例中,我们定义了一个名为Person的结构体和一个名为Employee的结构体。Employee结构体包含了一个Person类型的成员变量person,以及一个employee_id成员变量。通过结构体的嵌套,我们可以在一个结构体中包含另一个结构体,从而实现类似继承的关系,使得代码更加结构化和模块化。5.2.3 实战示例:结构体的继承和嵌套struct Shape {
color: String,
}
struct Rectangle {
shape: Shape,
width: u32,
height: u32,
}
struct Circle {
shape: Shape,
radius: f64,
}
fn main() {
let red_rectangle = Rectangle {
shape: Shape {
color: String::from("red"),
},
width: 10,
height: 20,
};
let blue_circle = Circle {
shape: Shape {
color: String::from("blue"),
},
radius: 5.0,
};
println!("Rectangle: {} x {}", red_rectangle.width, red_rectangle.height);
println!("Circle: radius {}", blue_circle.radius);
println!("Color: Rectangle - {}, Circle - {}", red_rectangle.shape.color, blue_circle.shape.color);
}在上述示例中,我们定义了一个名为Shape的结构体,以及两个派生结构体Rectangle和Circle。Rectangle和Circle结构体分别嵌套了Shape结构体,从而实现了继承和代码复用的效果。通过使用结构体的继承和嵌套,我们可以在Rust中模拟实现面向对象编程的特性,使得代码更加灵活和可扩展。5.3 使用结构体描述对象结构体可以被用来描述和表示现实世界中的对象,它们可以具有属性(字段)和行为(方法)。struct Person {
name: String,
age: u32,
}
impl Person {
fn introduce(&self) {
println!("My name is {} and I am {} years old.", self.name, self.age);
}
}fn main() {
let person = Person {
name: String::from("Alice"),
age: 25,
};
person.introduce();
}在上面的示例中,我们定义了一个名为Person的结构体,它具有name和age两个字段。通过为Person结构体实现introduce方法,我们可以在对象上调用该方法来介绍自己。在main函数中,我们创建了一个名为person的Person结构体实例,并通过调用introduce方法来打印自我介绍。通过使用结构体来描述对象,我们可以将对象的属性和行为封装在一起,实现数据和操作的高度内聚性,使得代码更加清晰和可维护。5.4 与基于类描述对象的对比Rust中的结构体和面向对象编程中的类有些相似,但也有一些不同之处。本章接下来将从以下几个方面与以 Java 为代表的典型基于类的面向对象编程语言进行对比:封装性继承性多态性所有权系统5.4.1 封装性Rust中的结构体可以使用pub关键字来控制字段和方法的可见性,实现封装。而面向对象的类默认具有公共接口,可以被外部访问。Rust 语言面向对象语言如JavaRust中的结构体可以使用 pub 关键字来控制字段和方法的可见性,从而实现封装。默认情况下,结构体的字段和方法是私有的,只能在同一模块内访问。面向对象语言如Java:面向对象语言通常使用访问修饰符(如 public、private)来控制类的成员的可见性。类的成员可以被其他类或对象访问。5.4.2 继承性Rust中的结构体没有直接的继承机制,但可以通过结构体的嵌套和 trait 的实现来达到类似的效果。Rust 语言面向对象语言如JavaRust中的结构体没有直接的继承机制。然而,可以通过结构体的嵌套和 trait 的实现来实现类似的功能。通过嵌套结构体,可以创建一个结构体,其中包含其他结构体作为其字段。面向对象语言中的继承允许一个类派生出另一个类,从而实现代码的复用。子类继承了父类的属性和方法,并且可以添加或重写这些成员。5.4.3 多态性Rust通过 trait 和泛型来实现多态性,不同类型的结构体可以实现相同的 trait,并以相同的方式进行处理。Rust 语言面向对象语言如JavaRust通过 trait 和泛型来实现多态性。不同类型的结构体可以实现相同的 trait,并以相同的方式进行处理。这使得代码更具灵活性,能够处理不同类型的对象。面向对象语言使用继承和接口来实现多态性。子类可以替代父类的位置,并以多态的方式使用。5.4.4 所有权系统Rust的所有权系统使得在处理对象时更加安全和高效,避免了一些内存管理的问题。Rust 语言面向对象语言如JavaRust的所有权系统确保了内存安全和资源管理。结构体在Rust中是拥有所有权的,当结构体被销毁时,它们的资源也被释放。这种所有权系统使得在处理对象时更加安全和高效。面向对象语言通常使用垃圾回收或手动内存管理来管理对象的生命周期和内存使用。6. 总结本文中我们深入探讨了Rust中的结构体(struct)的概念、用法和实际应用。我们首先介绍了结构体的基本定义和使用,包括如何声明结构体、定义字段和方法,并通过示例代码展示了结构体的基本操作。接着,我们详细讨论了结构体中的成员,包括成员变量和成员方法。我们介绍了如何在结构体中定义字段和方法,并通过示例演示了如何使用结构体的成员进行操作和访问。然后,我们讨论了结构体的实例化,包括创建结构体实例和结构体实例的初始化。我们介绍了通过new函数和简化的初始化语法来创建结构体实例,并通过示例代码展示了不同的实例化方式。接下来,我们探讨了结构体的实现,包括为结构体实现方法和 trait。我们详细介绍了如何为结构体定义方法,并通过示例代码展示了方法的使用。同时,我们讨论了如何为结构体实现 trait,以扩展结构体的功能和行为。在第五章中,我们与面向对象编程进行了比较,讨论了结构体与面向对象的特点和区别。我们介绍了结构体的继承和嵌套的实现方式,并通过示例代码展示了如何在Rust中模拟实现面向对象的特性。最后,我们在第六章对全文进行了总结。我们强调了Rust结构体的重要性和实际应用,并指出了结构体在代码设计和开发中的价值。通过使用结构体,我们可以将相关的数据和行为组织在一起,提高代码的可读性、可维护性和灵活性。
Rust 笔记Rust 语言中的字符串
Rust 笔记Rust 语言中的字符串作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130876665【介绍】:本文介绍 Rust 语言中的字符和字符串的用法。上一节:《 Rust 语言中使用 vector(向量) 》 | 下一节:《 Rust 语言中应用正则表达式 》目 录1. 概述2. Rust 的 字符 类型2.1 Rust 中的 字符(char)类型2.1.1 char 类型的概念2.1.2 在 char 类型 里 使用 转义字符2.1.3 char 类型 与其它类型的转换2.1.4 在字符(char)实例上提供的一些方法char 类型上用于判断的相关方法char 类型上用于转换的相关方法2.2 从字符到字符串2.2.1 什么是字符串2.2.2 字符 和 字符串的异同3. 不可变字符串类型 str3.1 `str` 类型概述3.2 不可变字符串str的特点3.2.1 不可变字符串str的优点3.2.2 不可变字符串str的不足3.2.3 如何选用 字符串str3.3 如何判断一个值的类型为str4. 可变字符串类型 String4.1 快速入门可变字符串 String4.1.1 什么是可变字符串4.1.2 String 类型 与 str 类型的区别4.1.3 如何创建String通过使用 String::new 方法创建通过使用 String::from 方法创建通过 str 类型的 to_string 方法创建通过 format! 宏创建4.2 String 用法解析4.2.1 字符串拼接4.2.2 字符串的切片4.2.3 clone 方法拷贝字符串4.2.4 trim 方法去除两端空白字符4.2.5 chars 方法及其应用String 的 chars 方法通过 chars 方法获取 字符串首字符通过 chars 方法获取 字符串尾字符通过 chars 方法获取 字符串的字符数通过 chars 方法 反转字符串4.2.6 to_uppercase 方法将字符串统一为大写字母形式4.2.7 to_lowercase 方法将字符串统一为大写字母形式4.2.8 len 方法获取字符串的长度4.2.9 contains 方法判断字符串是否包含指定的子串4.2.10 push 方法在字符串末尾添加单个字符4.2.11 push_str 方法在字符串末尾添加字符串字面量4.2.12 pop 方法移除字符串末尾的字符4.2.13 remove 方法移除字符串指定为序处的字符4.2.14 insert 方法在字符串指定位置插入内容4.2.15 replace_range 方法替换字符串指定范围内的内容4.2.16 truncate 方法截断字符串4.2.17 clear 方法清空字符串的内容4.2.18 字符串中使用转义符、原始字符串字符串中使用转义符原始字符串表示4.3 format! 宏 与 模板字符串的使用1. 概述本文介绍 Rust 语言中的 字符 和 字符串。在 Rust 语言中,不仅 字符 和 字符串 是不同的类型,并且还存在着 str 和 String 两种字符串。Rust 中的字符类型是 char,它表示 Unicode 标量值,占用 4 个字节,而字符串类型是 str 和 String。str 类型是一种不可变的字符串类型,通常使用 &str 来表示,而 String 类型是一种可变的字符串类型,通常使用 String 来表示。2. Rust 的 字符 类型2.1 Rust 中的 字符(char)类型2.1.1 char 类型的概念Rust 的 char 类型是该语言最原始的字母类型。该类型 在 Rust 表示 Unicode 标量值,它占用 4 个字节,可以使用 单引号 来表示一个 char 类型的值,例如:let a = 'a'; // 常规的 ASCII 字符
let b = '😎'; // Unicode 表情符号
let c = '\u{1F600}'; // 同样是 Unicode 表情符号,使用 Unicode 标量值表示2.1.2 在 char 类型 里 使用 转义字符现代高级语言中,如 C、JavaScript、Python,都使用 转义符 来表示一些特殊的字符, Rust 也不例外,其中的转义字符使用 反斜杠 (\)来表示,例如:let a = '\n'; // 换行符
let b = '\t'; // 制表符
let c = '\''; // 单引号由于反斜杠被用作 转义符,这意味着只有一个反斜杠表示的不是反斜杠自身,因此如果你要使用反斜杠,则加一个反斜杠对其进行转义,也就是:let d = '\\'; // 反斜杠在这一小节的例子中,我们使用的都是 单引号,可知这里的转义符针对的是 字符 类型。实际上 Rust 中,也可以将其用于 字符串 类型(在后面介绍)。2.1.3 char 类型 与其它类型的转换Rust 语言中提供了 as 关键字,与 TypeScript 中的 as 关键字仅仅是用于 类型断言 不同,在 Rust 语言中,as 的直接将 一个类型 转换为 另一个类型,而不仅仅是视作另一个类型。因此我们可以通过 as 将 char 类型转换为 u8 类型,或者将 u8 类型转换为 char 类型:let a = 'a';
let b = a as u8; // char 类型转换为 u8 类型
let c = b as char; // u8 类型转换为 char 类型或者let d = '😀';
let e = d as u32; // char 类型转换为 u32 类型
let f = e as char; // u32 类型转换为 char 类型2.1.4 在字符(char)实例上提供的一些方法我们可以调用 char 原生支持的一些方法,实现某些功能。char 类型上用于判断的相关方法方法描述is_ascii判断是否是 ASCII 字符is_alphabetic判断是否是字母is_digit判断是否是十进制数字is_lowercase判断是否是小写字母is_uppercase判断是否是大写字母例如:let a = 'A';
let m = a.is_ascii(); // 判断是否是 ASCII 字符
let n = a.is_alphabetic(); // 判断是否是字母
let o = a.is_digit(10); // 判断是否是十进制数字
let p = a.is_lowercase(); // 判断是否是小写字母
let q = a.is_uppercase(); // 判断是否是大写字母char 类型上用于转换的相关方法方法描述to_ascii_lowercase转换为小写字母to_ascii_uppercase转换为大写字母例如:let c = 'C';
let l = c.to_ascii_lowercase(); // 转换为对应的小写字母 => clet s = 's'
let u = s.to_ascii_uppercase(); // 转换为对应的大写字母 => S2.2 从字符到字符串2.2.1 什么是字符串顾名思义,将一串字符穿在一起就形成了 字符串。字符串是非常重要的数据类型,因为它们可以用于表示文本数据,例如文件内容、用户输入、网络数据等等。现实生活中的文本、句子都需要使用字符串而不是单个字符进行表示。字符串还可以用于格式化输出,例如将数字转换为字符串,或将字符串转换为数字。在Rust语言中,str类型的 字符串是一种不可变的数据类型,用于表示文本数据。字符串是 由字符组成的序列。str类型是一种不可变的字符串类型,通常使用&str来表示。String类型是一种可变的字符串类型,通常使用String来表示。2.2.2 字符 和 字符串的异同在 Rust 语言中,字符和字符串相比:字符类型(char)是单个字符,而 字符串 (str、String)是多个字符 组成的序列;字符类型(char)是不可变的,String 类型的字符串类型可以是可变的;字符类型通常使用 单引号表示,字符串类型通常使用 双引号 或者S tring::from方法 创建;字符 和 字符串 类型 都可以 使用一些特殊的 转义字符,如换行符、制表符等,字符串类型也支持这些转义字符。字符 类型 支持的一些方法,如 is_ascii、is_alphabetic、is_digit,同样在字符串中也支持。3. 不可变字符串类型 str3.1 str 类型概述在 Rust 语言中,str 类型 是一种不可变的字符串类型,通常使用 &str 来表示。创建 str 类型 的方法有很多,比如最简单的就是使用 双引号语法,该方法可以非常方便地创建 str 字面量:let s1 = "hello world!"; // 直接使用字符串字面量创建
let s2: &str = "hello"; // 使用类型注解创建str 也可以是通过可变字符串 String转换来的:let s3 = String::from("world").as_str(); // 使用 String 类型的 as_str 方法创建或者使用可变字符串 String 类型的引用创建不可变字符串 str 类型地字面量:let s4 = &String::from("world"); // 使用 String 类型的引用创建3.2 不可变字符串str的特点3.2.1 不可变字符串str的优点不可变字符串具有以下有点安全性高:不可变字符串不会被意外修改,避免了一些潜在的错误。性能高:不可变字符串的内存布局更加紧凑,访问速度更快。3.2.2 不可变字符串str的不足不可变字符串也有一些不足,比如:不可变字符串无法修改,需要重新创建一个新的字符串来实现修改操作,会占用更多的内存空间。不可变字符串无法直接拼接,需要使用 format! 宏 或者 String::from 方法来实现。3.2.3 如何选用 字符串str一般以下,可以参考以下建议选择字符串:字符串不需要修改时,选用不可变字符串 str ,这样更加 安全 和 高效;反之,字符串需要修改时,选用可变字符串 String。3.3 如何判断一个值的类型为str判断一个值是否是 str 类型可以使用类型判断语法:fn is_str(s: &dyn std::any::Any) -> bool {
s.is::<&str>()
}例如:let s1 = "hello";
let s2 = String::from("world");
let s3 = 123;
let s4 = vec![1, 2, 3];
println!("{}", is_str(&s1)); // true
println!("{}", is_str(&s2)); // false
println!("{}", is_str(&s3)); // false
println!("{}", is_str(&s4)); // false4. 可变字符串类型 String4.1 快速入门可变字符串 String4.1.1 什么是可变字符串可变字符串的 可变 是相对于不可变字符串而言的,我们上一节使用字面量语法创建的字符串已经创建就不可以修改了,因而称之为不可变字符串。换而言之,可变字符串的可变指的是字符串的内容可以被修改。例如,使用 push_str 方法可以在字符串末尾添加字符串字面量:let mut s1 = String::from("hello");
s1.push_str(", world!"); // 在字符串末尾添加字符串字面量
println!("{}", s1); // 输出:hello, world!可以看到,通过这样的方法,使得字符串的内容发生了变化。类似的方法还有很多,请参考 4.2 String 方法解析 部分。4.1.2 String 类型 与 str 类型的区别str 类型是不可变的,而 String 类型是可变的。str 类型通常用于函数参数和返回值,而 String 类型通常用于变量和常量。str 类型通常使用 字符串字面量 创建,而 String 类型通常使用 String::from 方法 创建。关于创建 String 的方法,请参考下一小节4.1.3 如何创建String通过使用 String::new 方法创建使用 String::new 方法可以创建一个空的 String 类型的实例,例如:let s1 = String::new(); // 创建一个空的 String 类型通过使用 String::from 方法创建String::from 方法可以直接使用字符串字面量创建 String,例如:let s2 = String::from("hello");实际上,你也可以理解为将一个 str 转换为对应的 String。通过 str 类型的 to_string 方法创建不可变字符串 str 上提供了一个 to_string 方法,返回对应的 String 字符串。例如:let s3 = "world".to_string();通过 format! 宏创建你还可以使用 format! 宏 来创建 String 字符串。例如:let s4 = format!("{} {}", "hello", "world");关于 format! 宏 ,请参考 4.3 format! 宏 与 模板字符串的使用4.2 String 用法解析4.2.1 字符串拼接使用 + 操作符可以用于拼接两个字符串例如:let s1 = String::from("Hello ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 使用 + 运算符连接两个字符串
println!("{}", s3); // 输出:Hello world!4.2.2 字符串的切片所谓 切片,指的是通过指定一对起止字符在字符串中的 位序,从而获得一个子字符串的方法。和诸多编程语言一样(如Python),可以使用 [] 语法对字符串进行切片。如:let s1 = String::from("hello");
let s2 = &s1[0..2]; // 使用切片获取字符串的一部分
println!("{}", s2); // 输出:he4.2.3 clone 方法拷贝字符串String 的 clone 方法从原 String 对象实例上拷贝得到一个副本,例如:let s1 = String::from("hello");
let s2 = s1.clone(); // 使用 clone 方法复制一个字符串
println!("{}", s2); // 输出:hello4.2.4 trim 方法去除两端空白字符在字符串处理的时候经常需要处理掉字符串两端的空白字符,使用 String 的trim 方法即可轻松实现,例如:例如:let s1 = String::from("hello");
let s2 = s1.trim();
println!("{}", s9); // 输出:hello4.2.5 chars 方法及其应用String 的 chars 方法String 的chars 方法用于获取字符串的字符迭代器。例如:let s1 = String::from("hello");
let s2 = s1.chars(); // 获取字符串的字符迭代器
for c in s23 {
println!("{}", c); // 逐个输出字符串的字符
}通过 chars 方法获取 字符串首字符例如:let s1 = String::from("hello");
let s2 = s1.chars().nth(0).unwrap(); // 获取字符串的第一个字符
println!("{}", s2); // 输出:h通过 chars 方法获取 字符串尾字符例如:let s1 = String::from("hello");
let s2 = s1.chars().last().unwrap(); // 获取字符串的最后一个字符
println!("{}", s2); // 输出:o通过 chars 方法获取 字符串的字符数例如:let s1 = String::from("hello");
let s2 = s1.chars().count(); // 获取字符串的字符数
println!("{}", s2); // 输出:5通过 chars 方法 反转字符串例如:let s1 = String::from("hello");
let s2 = s1.chars().rev().collect::<String>(); // 将字符串反转
println!("{}", s2); // 输出:olleh4.2.6 to_uppercase 方法将字符串统一为大写字母形式例如:let s1 = String::from("hEllo");
let s2 = s1.to_uppercase(); // 将字符串转换为大写字母形式
println!("{}", s2); // 输出:HELLO4.2.7 to_lowercase 方法将字符串统一为大写字母形式例如:let s1 = String::from("heLLo");
let s2 = s1.to_lowercase(); // 将字符串转换为小写字母形式
println!("{}", s2); // 输出:hello4.2.8 len 方法获取字符串的长度例如:let length = String::from("world").len(); // 54.2.9 contains 方法判断字符串是否包含指定的子串例如:let isContains = String::from("Hello").contains("llo"); // true4.2.10 push 方法在字符串末尾添加单个字符例如:let mut s = String::from("Hello");
s.push('!');
println!("{}", s); // 输出:Hello!4.2.11 push_str 方法在字符串末尾添加字符串字面量例如:let mut s = String::from("hello");
s.push_str(", world!");
println!("{}", s); // 输出:hello, world!4.2.12 pop 方法移除字符串末尾的字符例如:let mut s = String::from("hello!");
s.pop(); // 移除字符串末尾的字符
println!("{}", s); // 输出:hello4.2.13 remove 方法移除字符串指定为序处的字符例如:let mut s = String::from("hello");
s.remove(2);
println!("{}", s); // 输出:helo4.2.14 insert 方法在字符串指定位置插入内容内容可以是 char 和 str。例如在字符串指定位置插入单个字符:let mut s = String::from("helo");
s.insert(2, 'l'); //
println!("{}", s); // 输出:hello又如在字符串指定位置插入字符串字面量:let mut s = String::from("heo");
s.insert(1, "ll");
println!("{}", s); // 输出:hello4.2.15 replace_range 方法替换字符串指定范围内的内容例如:let mut s = String::from("hello");
s.replace_range(1..3, "a");
println!("{}", s); // 输出:hallo4.2.16 truncate 方法截断字符串truncate 方法可以用于截断字符串,只保留指定长度的内容。例如:let mut s = String::from("hello");
s.truncate(3);
println!("{}", s); // 输出:hel4.2.17 clear 方法清空字符串的内容字符串是字符的容器,清空字符串就得到一个没有任何字符的字符串,也称空字符串。例如:let mut s = String::from("hello");
s.clear();
println!("{}", s); // 输出:4.2.18 字符串中使用转义符、原始字符串字符串中使用转义符在介绍 Rust 字符的时候我们介绍过转义符,Rust 中的字符串类型也支持转义字符,例如:let s1 = "hello\nworld"; // 换行符
let s2 = "hello\tworld"; // 制表符
let s3 = "hello\'world"; // 单引号
let s4 = "hello\\world"; // 反斜杠也可以使用转义符表示 Unicode 标量值,例如:let s1 = "\u{1F600}"; // Unicode 表情符号
let s2 = "\u{1F600}\u{1F601}\u{1F602}"; // 多个 Unicode 表情符号原始字符串表示在 Rust 中,字符串前的 r# 表示使用原始字符串表示法,不会转义字符。例如:let s1 = r#"hello\nworld"#;
println!("{}", s1); // 输出:hello\nworld原始字符串表示法还支持使用多个 # 号,例如:let s2 = r##"hello "world""##;
println!("{}", s2); // 输出:hello "world"使用原始字符串表示法可以避免一些转义字符带来的麻烦,比如在正则表达式中使用反斜杠。例如:let s3 = r"\d+";
println!("{}", s3); // 输出:\d+4.3 format! 宏 与 模板字符串的使用Rust 中的 format! 宏 用于格式化字符串,比如:let s1 = format!("{} {}", "hello", "world"); // 使用位置占位符
let s2 = format!("{name} {age}", name = "Alice", age = 18); // 使用命名占位符
let s3 = format!("{:?}", vec![1, 2, 3]); // 将 Vec 转换为字符串format! 宏 还支持一些高级特性,如:let s4 = format!("{:x}", 255); // 将数字转换为十六进制字符串
let s5 = format!("{:b}", 255); // 将数字转换为二进制字符串
let s6 = format!("{:o}", 255); // 将数字转换为八进制字符串
let s7 = format!("{:.2}", 3.1415926); // 将浮点数保留两位小数
let s8 = format!("{:+}", 123); // 将数字添加正负号
let s9 = format!("{:*>10}", "hello"); // 将字符串右对齐并填充 *
let s10 = format!("{:^10}", "hello"); // 将字符串居中对齐
Rust 笔记Rust 语言中的运算符
Rust 笔记Rust 语言中的运算符作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/131013679【介绍】:本文讲解 Rust 语言中的运算符。上一节:《 暂无》 | 下一节:《 在 Rust 语言中使用重载 》目 录1. 概述2. 一元运算符2.1 关于 一元运算符2.2 正(+)运算符2.3 负(-)运算符2.4 逻辑非(!)运算符2.5 递增(++)和递减(--)运算符2.6 取址(&)运算符2.7 解引用(*)运算符3. 二元运算符3.1 关于 二元运算符3.2 算术运算符3.2.1 加法(+)运算符3.2.1 减法(-)运算符3.2.3 乘法(*)运算符3.2.3 除法(/)运算符3.3 逻辑运算符3.3.1 逻辑与(&&)3.3.2 逻辑或(||)3.4 比较运算符3.4.1 等于(==)3.4.2 不等于(!=)3.4.3 大于运算符(>)3.4.4 小于运算符(<)3.4.5 大于等于运算符(>=)3.4.6 小于等于运算符(<=)3.5 赋值运算符3.5.1 基本赋值(=)运算符3.5.2 复合赋值运算符(如 +=、-=、*=、/= 等)3.6 位运算符3.6.1 按位与(`&`)运算符3.6.2 按位或(`|`)运算符3.6.3 按位异或(`^`)运算符3.6.4 左移(`<<`)运算符3.6.5 右移(`>>`)运算符3.7 索引(`[]`)运算符3.7.1 数组的索引3.7.2 向量的索引3.7.3 字符串的索引3.8 调用(`()`)运算符3.9 解引用赋值(`*=`)运算符1. 概述在各种编程语言中,运算符(Operators)是用于执行各种操作的符号或关键字。它们可以用于操作数据、执行逻辑判断、进行赋值等。根据其功能和操作数的数量,运算符可以被分类为以下几种类型:一元运算符:一元运算符作用于单个操作数,对其进行特定操作或计算。二元运算符:二元运算符作用于两个操作数,对其进行特定操作或计算。三元运算符:三元运算符是一种特殊的运算符,接受三个操作数,并根据第一个操作数的条件返回第二个或第三个操作数。注:Rust 语言中无三元运算符。逻辑运算符:逻辑运算符用于执行逻辑操作,例如逻辑与、逻辑或和逻辑非。比较运算符:比较运算符用于比较两个值之间的关系,并返回布尔值(true 或 false)。算术运算符:算术运算符用于执行数学运算,如加法、减法、乘法和除法等。位运算符:位运算符用于对二进制位进行操作,如按位与、按位或、按位异或等。索引运算符:它需用于从 被索引的操作数 凭借 索引值 获取结果返回。以上的类别是有重叠的,比如 逻辑运算符 等也是一元运算符, 算术运算符、索引运算符 等同时也是 二元运算符。在本文的介绍中,如果在前面的类别中已经讲解过,则后文不再重复讲解。2. 一元运算符2.1 关于 一元运算符一元运算符(Unary Operators)又称之为单目运算符,指的是只对一个表达式执行操作,该表达式可以是数值数据类型类别中的任何一种数据类型。Rust 语言 中的一元运算符包括 加(+)、减(-)、**逻辑非(!)**和 取地址(&) 等。但是整体上看,都可以视为一元或者二元运算符,并且 rust 语言不提供内置的三元操作符。2.2 正(+)运算符【注意】:+ 用于 取正 时为 一元运算符,但是用于 加法 时则为 二元运算符。另请参见 [加运算符加(+) 运算符 用于对操作数 取正,其实没有实际变化。let a = 10;
let b = +a; // 取 a 的正值(实际上没有变化)
println!("b: {}", b); // 输出: b: 102.3 负(-)运算符负(-) 运算符 用于用于对操作数取负。例如:let a = 10;
let b = -a; // 取 a 的负值
println!("b: {}", b); // 输出: b: -102.4 逻辑非(!)运算符逻辑非(!) 运算符 用于 对 布尔类型 的操作数 取反。包括以下两种情况:如果操作数的值为 true,则将 true 变为 false;如果操作数的值为 false,则将 false 变为 true例如:let a = true;
let b = !a; // 对 a 取逻辑非
println!("b: {}", b); // 输出: b: false2.5 递增(++)和递减(–)运算符递增(++) 和 **递减(–)**运算符 分别用于 对操作数 进行 自增 和 自减 操作。例如:// 自增操作
let mut a = 10;
a += 1; // 等价于 a = a + 1
println!("a: {}", a); // 输出: a: 11
// 自减操作
let mut b = 5;
b -= 1; // 等价于 b = b - 1
println!("b: {}", b); // 输出: b: 42.6 取址(&)运算符取址(&) 运算符用于 获取变量的内存地址。例如:let a = 10;
let b = &a; // 获取变量 a 的内存地址
println!("b: {:?}", b); // 输出: b: 0x7ffc8680d1b8(具体的地址可能不同)2.7 解引用(*)运算符解引用运算符 * 用于从 引用类型 中获取 引用所指向的值。它允许你访问引用类型中存储的实际值。例如:let num = 42;
let num_ref = &num;
let dereferenced_num = *num_ref; // 使用解引用运算符获取引用所指向的值
println!("Dereferenced num: {}", dereferenced_num); // 输出: Dereferenced num: 42
let mut value = 10;
let value_ref = &mut value;
*value_ref = 20; // 使用解引用运算符修改引用所指向的值
println!("Modified value: {}", value); // 输出: Modified value: 203. 二元运算符3.1 关于 二元运算符类似地,二元运算符(Binary Operators)是一种作用于两个操作数的运算符。在各种编程语言中一般用于表示由两个元素形成第三个元素的一种规则,它们往往用于执行各种操作,如加法、减法、乘法、除法、逻辑运算等。在 Rust 语言中,对二元运算符进行细分还可以分为:算术运算符:用于执行数值计算;逻辑运算符:用于执行逻辑操作;赋值运算符:用于将 右侧的值 赋给 左侧的变量;比较运算符:用于对两个值进行比较并得出结论;3.2 算术运算符3.2.1 加法(+)运算符加法(+) 运算符用于对两个操作数进行相加。例如:let a = 5;
let b = 3;
let c = a + b; // 将 a 和 b 相加
println!("c: {}", c); // 输出: c: 83.2.1 减法(-)运算符减法(-) 运算符用于从第一个操作数中减去第二个操作数。例如:let a = 5;
let b = 3;
let c = a - b; // 从 a 中减去 b
println!("c: {}", c); // 输出: c: 23.2.3 乘法(*)运算符乘法(*) 运算符用于将两个操作数相乘。例如:let a = 5;
let b = 3;
let c = a * b; // 将 a 和 b 相乘
println!("c: {}", c); // 输出: c: 153.2.3 除法(/)运算符除法(/) 运算符用于将第一个操作数除以第二个操作数。例如:let a = 10;
let b = 2;
let c = a / b; // 将 a 除以 b
println!("c: {}", c); // 输出: c: 53.3 逻辑运算符3.3.1 逻辑与(&&)逻辑与(&&) 运算符 用于对两个布尔类型的操作数进行逻辑与操作。例如:let a = true;
let b = false;
let c = a && b; // 对 a 和 b 执行逻辑与操作
println!("c: {}", c); // 输出: c: false3.3.2 逻辑或(||)逻辑或(||) 运算符用于对两个布尔类型的操作数进行逻辑或操作。例如:let a = true;
let b = false;
let c = a || b; // 对 a 和 b 执行逻辑或操作
println!("c: {}", c); // 输出: c: true3.4 比较运算符3.4.1 等于(==)相等性(==) 运算符用于检查两个操作数是否相等。例如:let a = 2;
let b = 2;
let c = a == b; // 检查 a 和 b 是否相等
println!("c: {}", c); // 输出: c: true3.4.2 不等于(!=)不相等(!=) 运算符用于检查两个操作数是否不等。例如:let a = 1;
let b = 3;
let c = a != b; // 检查 a 和 b 是否不相等
println!("c: {}", c); // 输出: c: true3.4.3 大于运算符(>)大于运算符(>) 用于检查左操作数是否大于右操作数。let a = 2;
let b = 1;
let result = a > b; // 结果为 true3.4.4 小于运算符(<)小于运算符(<) 用于 检查左操作数是否小于右操作数。例如:let a = 2;
let b = 1;
let result = a < b; // 结果为 false3.4.5 大于等于运算符(>=)大于等于运算符(>=) 用于检查左操作数是否大于或等于右操作数。例如:let a = 2;
let b = 1;
let result = a >= b; // 结果为 true3.4.6 小于等于运算符(<=)小于等于运算符(<=) 用于检查左操作数是否小于或等于右操作数。例如:let a = 2;
let b = 1;
let result = a <= b; // 结果为 false3.5 赋值运算符3.5.1 基本赋值(=)运算符赋值(=) 运算符 用于将右侧的值赋给左侧的变量。例如:let a = 1; // 将值 1 赋给变量 a3.5.2 复合赋值运算符(如 +=、-=、*=、/= 等)在赋值运算符的基础上,结合其它运算符就形成了功能略多的 复合赋值运算符(如 +=、-=、*=、/= 等)。 复合赋值运算符用于将运算符右侧的值与左侧的变量进行运算,并将结果赋给左侧的变量。let mut a = 1;
a += 2; // 等价于 a = a + 2
println!("a: {}", a); // 输出: a: 33.6 位运算符位运算符用于对整数类型的二进制位进行操作,也有两个操作数,因此同样可视为二元运算符。在 Rust 语言中,位运算符包括:按位与运算符(&)按位或运算符(|)按位异或运算符(^)左移运算符(<<)右移运算符(>>)3.6.1 按位与(&)运算符按位与运算符(&)用于对两个操作数的对应二进制位进行与操作,结果为每个对应位都为1时才为1,否则为0。let a = 2;
let b = 3;
let result = a & b; // 对 a 和 b 的二进制位进行与操作3.6.2 按位或(|)运算符按位或运算符(|)用于对两个操作数的对应二进制位进行或操作,结果为每个对应位都为0时才为0,否则为1。let a = 5;
let b = 3;
let result = a | b; // 对 a 和 b 的二进制位进行或操作3.6.3 按位异或(^)运算符按位异或运算符(^)用于对两个操作数的对应二进制位进行异或操作,结果为每个对应位相同为0,不同为1。let a = 5;
let b = 3;
let result = a ^ b; // 对 a 和 b 的二进制位进行异或操作3.6.4 左移(<<)运算符左移运算符(<<)用于将操作数的二进制位向左移动指定的位数,右侧空出的位用0填充。例如:let a = 5; // 二进制表示为 0101
let result = a << 2; // 将二进制位左移2位,结果为 10100,即 203.6.5 右移(>>)运算符右移运算符(>>)用于将操作数的二进制位向右移动指定的位数,左侧空出的位用0或符号位填充(取决于操作数的符号)。例如:let a = 5; // 二进制表示为 0101
let result = a >> 1; // 将二进制位右移1位,结果为 0010,即 23.7 索引([])运算符索引运算符 [] 被视需要两个操作数:被索引的数据结构 和 索引值。其中,被索引的数据结构 是 左操作数,索引值 是 右操作数。通过将索引值放在方括号内,可以使用索引运算符访问数据结构中的特定元素。被索引的数据结构 可以是很多种,比如数组、向量、字符串等等。3.7.1 数组的索引Rust 数组内置索引能力,可以通过索引运算符按照位序获取元素。例如:let arr = [1, 2, 3, 4, 5];
let element = arr[2]; // 获取索引为2的元素,结果为3
arr[3] = 10; // 修改索引为3的元素为103.7.2 向量的索引Rust 向量内置索引能力,可以通过索引运算符按照位序获取元素。例如:let vec = vec![1, 2, 3, 4, 5];
let element = vec[3]; // 获取索引为3的元素,结果为4
vec[1] = 7; // 修改索引为1的元素为73.7.3 字符串的索引《Rust 语言中的字符串》 中详细介绍了 str 和 String。其中我们说过字符串类型 str 是一种不可变的的字符,这种字符串类型 不支持通过索引运算符直接对其进行索引。这是因为 str 类型是一个不固定长度的 UTF-8 字符串,直接通过索引访问可能会导致不正确的操作。好在 Rust 语言中还有一种可变字符串String。String 类型是支持通过索引运算符对其进行索引的。它可以使用索引运算符 [] 并传递一个整数索引来访问 String 中的字符。例如:let mut s = String::from("Hello, Rust!");
// 通过索引访问和修改字符
let first_char = s[0];
s[7] = 'W';
println!("First character: {}", first_char);
println!("Modified string: {}", s);3.8 调用(())运算符调用运算符 () 用于调用函数或方法,将函数或方法名称与一对括号组合在一起,允许你执行函数或方法并传递参数。例如我们可以定义一个独立的函数,或者在结构体中定义一个方法:fn hello(name: &str) {
println!("Hello, {}!", name);
}
struct Person {
name: String,
}
impl Person {
fn greet(&self) {
println!("Hello, {}!", self.name);
}
}然后在需要使用的地方通过 调用运算符 调用它们:fn main() {
hello("Alice"); // 调用函数
// 调用方法
let person = Person {
name: String::from("Bob"),
};
person.greet(); // 输出:Hello, Bob!
}调用运算符 () 可以用于任何 可调用项,包括函数、方法、闭包等。它可以带有参数,用于向被调用项传递值。3.9 解引用赋值(*=)运算符解引用赋值运算符(*=)是一种 复合运算符,它由之前介绍过的 解引运算符 和 赋值运算符 复合而成。*= 用于 解引用一个可变引用,并将 新的值 赋给 引用所指向的位置。它将 右侧的值 解引用,并将 解引用后的值 赋给 左侧的可变引用。 例如:let num = 42;
let num_ref = &num;
// 使用解引用运算符获取引用所指向的值
let dereferenced_num = *num_ref;
println!("Dereferenced num: {}", dereferenced_num); // 输出: Dereferenced num: 42
let mut value = 10;
let value_ref = &mut value;
// 使用解引用运算符修改引用所指向的值
*value_ref = 20;
println!("Modified value: {}", value); // 输出: Modified value: 20
WebAssembly 的 JavaScript API
WebAssemblyWebAssembly 的 JavaScript API作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130879208【介绍】:本文介绍 WebAssembly 的 JavaScript API。上一节:《 如何加载和运行 WebAssembly 代码 》 | 下一节:《 暂无 》目 录1. 概述1.1 引言1.2 为什么需要用于 WebAssembly 的 JavaScript API1.3 ★ 浏览器 和 NodeJS 环境加载 wasm 模块 差异1.3.1 浏览器环境下1.3.2 NodeJS 环境下2. WebAssembly JavaScript API 详解2.1 API 概述2.1.1 JavaScript 的 WebAssembly 对象是一个命名空间对象2.1.2 API 一览表普通函数用作构造器函数2.2 WebAssembly API 中的普通函数2.2.1 instantiate() 函数2.2.2 instantiateStreaming() 函数2.2.3 compile() 函数2.2.4 compileStreaming() 函数2.3 WebAssembly API 中的构造函数2.3.1 Module() 构造函数 与 Module 对象WebAssembly.Module 对象的构造函数Module 对象解析customSections 方法exports 方法imports 方法2.3.2 Global() 构造函数 与 Global 对象WebAssembly.Global 对象的构造函数Global 对象解析2.3.3 Instance() 构造函数 与 Instance 对象WebAssembly.Instance 对象的构造函数WebAssembly.Instance 对象2.3.4 Memory() 构造函数 与 Memory 对象WebAssembly.Memory 对象的构造函数WebAssembly.Memory 对象2.3.5 Table() 构造函数 与 Table 对象WebAssembly.Table 对象的构造函数WebAssembly.Table 对象2.3.6 RuntimeError() 构造函数 与 RuntimeError 对象WebAssembly.RuntimeError 对象的构造函数WebAssembly.RuntimeError 对象2.3.7 CompileError() 构造函数 与 CompileError 对象WebAssembly.CompileError 对象的构造函数WebAssembly.CompileError 对象2.3.8 LinkError() 构造函数 与 LinkError 对象WebAssembly.LinkError 对象的构造函数WebAssembly.LinkError 对象2.3.9 Exception() 构造函数 与 Exception 对象WebAssembly.Exception 对象的构造函数WebAssembly.Exception 对象实例属性 stack2.3.10 Tag() 构造函数 与 Tag 对象WebAssembly.Tag 对象的构造函数WebAssembly.Tag 对象type() 方法1. 概述1.1 引言在之前的文章中我们讲解过将某些语言(如 Rust、 C++ 等)编译成 WebAssembly ,也简单讲解过 如何加载和运行 WebAssembly 代码 。当加载了一个 .wasm 模块 之后,你就想要去使用它。WebAssembly 的 JavaScript API, 就为我们提供了这样的交互能力。 本文在前面已经对 WebAssembly 有一定的使用基础上,争取对 WebAssembly 的 JavaScript API 有全面把握,以为应用自如打好基础。1.2 为什么需要用于 WebAssembly 的 JavaScript APIWebAssembly 是一种可移植、高性能的二进制格式,可以在现代 Web 浏览器中运行。虽然 WebAssembly 可以直接由编译器生成并在浏览器中运行,但为了能够与 JavaScript 交互和利用 Web 平台的功能,WebAssembly 需要使用 JavaScript API,比如用于操作 DOM,等等。归纳起来,WebAssembly 目前仍然需要使用 JavaScript API 的主要原因有以下几点:当前,WebAssembly 还没有直接访问浏览器 API 的能力。WebAssembly 本身只是一种二进制格式,没有直接访问浏览器 DOM、网络请求、图形渲染 等能力。通过 JavaScript API,WebAssembly 可以与 JavaScript 代码进行交互,并利用 JavaScript 来访问浏览器提供的功能。JavaScript API 提供了底层数据传输和交互的机制。WebAssembly 与 JavaScript 之间需要进行数据的传递和交互。JavaScript API 提供了一系列的函数和对象,用于在 WebAssembly 模块和 JavaScript 之间传递数据、调用函数、导入导出功能等。1.3 ★ 浏览器 和 NodeJS 环境加载 wasm 模块 差异本文后续介绍各个 API 中涉及大量的代码示例,这些示例都是假定在浏览器中使用的。实际上在实际开发中,也经常应用于 NodeJS 环境,这里会有一些不同。在这一小节我们提前把这里的问题讲清楚,后面则不再对该问题进行赘述。1.3.1 浏览器环境下浏览器环境下我们使用 Fetch API 起 HTTP 请求来获取 WebAssembly 模块,因为:WebAssembly 模块 通常是作为 独立的二进制文件存在,而不是内联在 HTML 文件中。因此,需要通过网络从服务器获取模块文件。WebAssembly 模块可能具有较大的文件大小,使用 HTTP 请求可以利用浏览器的缓存机制,减少模块的重复下载。Fetch API 提供了一种方便的方式来异步获取资源。它支持 Promise 和 async/await 语法,使得处理异步操作更加简洁和可读。使用 Fetch API 发起请求可以通过设置请求头、处理错误和响应等进行更灵活的控制。总之,使用 Fetch API 发起 HTTP 请求来获取 WebAssembly 模块是为了从服务器异步获取模块文件,并能够灵活地处理请求和响应过程。这样可以实现模块的动态加载和更好的控制。1.3.2 NodeJS 环境下在 Node.js 环境中,由于没有浏览器的环境和 Fetch API,不能直接使用 fetch 函数来获取 WebAssembly 模块(除非你硬要搭建一个静态文件服务器,再来使用代码请求到模块代码来运行)。通常情况下在 Node.js 环境中我们使用 fs 模块 来读取 模块文件 并 获取其 二进制数据。具体的,在 NodeJS 中,你可以参考下面示例给出的方式 加载 WebAssembly 模块:使用 fs.readFileSync 读取 WebAssembly 模块的二进制文件,并使用 WebAssembly.compile 编译模块。const fs = require('fs');
const buffer = fs.readFileSync('module.wasm');
const module = new WebAssembly.Module(buffer);使用 WebAssembly.instantiate 或 WebAssembly.instantiateStreaming 方法直接实例化 WebAssembly 模块。const fs = require('fs');
const buffer = fs.readFileSync('module.wasm');
const module = new WebAssembly.Module(buffer);
WebAssembly.instantiate(module).then(instance => {
// ...
});2. WebAssembly JavaScript API 详解2.1 API 概述2.1.1 JavaScript 的 WebAssembly 对象是一个命名空间对象在逐个讲解前,我们一定要先注意,类似于 Math 对象 或者 Intl 对象:JavaScript 中,WebAssembly 不是一个构造函数(它不是一个函数对象),而是所有 WebAssembly 相关功能的 命名空间。——和大多数全局对象不一样。一般我们主要通过 WebAssembly 对象上提供的:WebAssembly.instantiate() 函数 加载 编译好的 加载 WebAssembly 代码;WebAssembly.Memory() 构造函数 创建新的内存实例;WebAssembly.Table() 构造函数 创建新的表实例;WebAssembly.CompileError() 构造函数 来提供 WebAssembly 中的编译错误信息;WebAssembly.LinkError() 构造函数 来提供 WebAssembly 模块实例化期间的错误信息;WebAssembly.RuntimeError() 构造函数 来提供 WebAssembly 中的运行错误信息。2.1.2 API 一览表普通函数API描述instantiate()编译和实例化 WebAssembly 代码的主要的 API,它返回一个 Module 及其第一个 Instance 实例。instantiateStreaming()直接从流式底层源编译和实例化 WebAssembly 模块,同时返回 Module 及其第一个Instance实例。compile()把 WebAssembly 二次代码编译为一个 WebAssembly.Module,不进行实例化。compileStreaming()直接从流式底层源代码编译 WebAssembly.Module,将实例化作为一个单独的步骤用作构造器函数这些函数用于通过 JavaScript 函数的 构造调用 以创建他们表示的同名对象实例,相当于基于类的面向对象编程语言中某个类的构造方法,因此用的都是大写字母开头。不了解的可以参考我的另外一篇博文 https://blog.csdn.net/qq_28550263/article/details/123418894。API描述说明Global(descriptor, value)创建一个新的 Global 对象实例。Global 对象表示一个全局变量实例,可以被 JavaScript 和 importable/exportable 访问 ,跨越一个或多个WebAssembly.Module 实例。他允许被多个 modules 动态连接。Module(bufferSource)创建一个新的 Module 对象实例。Module 对象包含已经由浏览器编译的无状态 WebAssembly 代码,可以高效地与 Worker 共享和多次实例化。Memory(memoryDescriptor)创建一个新的 Memory 对象实例。Memory 对象是一个可调整大小的ArrayBuffer或SharedArrayBuffer,用于保存 WebAssembly.Instance 访问的原始内存字节。Table(tableDescriptor)创建一个新的 Table 对象实例。CompileError(message, fileName, lineNumber)创建一个新的 CompileError 对象实例。CompileError 对象表示 WebAssembly 解码或验证期间的错误。LinkError(message, options)创建一个新的 LinkError 对象实例。LinkError 对象指示模块实例化期间的错误(除了来自start函数的 traps 之外)。RuntimeError(message, options)创建一个新的 RuntimeError 对象实例。Instance(module, importObject)创建一个新的 Instance 对象实例。Instance 对象包含所有 导出的 WebAssembly 函数,这些函数允许从 JavaScript 调用 WebAssembly 代码。Tag(type)创建一个新的 Tag 对象实例。.Tag对象定义了一种可以向 WebAssembly 代码抛出或从中抛出的 WebAssembly 异常类型。(这是一个新API)Exception(tag, payload, options)创建一个新的 Exception 对象实例。Exception对象表示从 WebAssembly 抛出到 JavaScript 或 从 JavaScript 抛出到 WebAssembly 异常处理程序的 运行时异常。(这是一个新API)2.2 WebAssembly API 中的普通函数2.2.1 instantiate() 函数该方法从已编译的 WebAssembly 模块创建一个实例。其语法格式为:WebAssembly.instantiate(bufferSource, importObject)其中参数:bufferSource:包含 WebAssembly 模块字节码的 ArrayBuffer、TypedArray 或 DataView。importObject(可选):一个对象,用于传递给 WebAssembly 模块的导入对象。例如:// 发起对 WebAssembly 模块的 HTTP 请求
const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 使用 ArrayBuffer 创建 WebAssembly 模块实例
const module = await WebAssembly.instantiate(buffer);
// 获取 WebAssembly 实例对象
const instance = module.instance;2.2.2 instantiateStreaming() 函数该方法通过流式加载已编译的 WebAssembly 模块,并创建一个实例。其语法格式为:WebAssembly.instantiateStreaming(source, importObject)其中参数:source:一个表示可通过 HTTP 请求获取 WebAssembly 模块的 URL 字符串或 Response 对象。importObject(可选):一个对象,用于传递给 WebAssembly 模块的导入对象。例如:// 流式加载并实例化 WebAssembly 模块
const module = await WebAssembly.instantiateStreaming(fetch('module.wasm'));
// 获取 WebAssembly 实例对象
const instance = module.instance;2.2.3 compile() 函数该方法将 WebAssembly 模块的字节码编译成一个可执行的模块对象。其语法格式为:WebAssembly.compile(bufferSource)其中参数:bufferSource:包含 WebAssembly 模块字节码的 ArrayBuffer、TypedArray 或 DataView。例如:// 发起对 WebAssembly 模块的 HTTP 请求
const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);2.2.4 compileStreaming() 函数该方法通过流式加载 WebAssembly 模块的字节码,并将其编译成一个可执行的模块对象。其语法格式为:WebAssembly.compileStreaming(source)其中参数:source:一个表示可通过 HTTP 请求获取 WebAssembly 模块的 URL 字符串或 Response 对象。例如:const module = await WebAssembly.compileStreaming(fetch('module.wasm'));2.3 WebAssembly API 中的构造函数2.3.1 Module() 构造函数 与 Module 对象WebAssembly.Module 对象的构造函数WebAssembly.Module() 构造函数 用于创建一个 WebAssembly.Module 实例对象。它接受一个 ArrayBuffer 作为参数,并返回一个模块对象,用于后续的实例化操作。该构造函数的类型签名即下一节 Module 对象的类型签名 中的 new 方法的签名,这里不重复给出。例如:fetch('module.wasm')
.then(response => response.arrayBuffer())
.then(buffer => {
const module = new WebAssembly.Module(buffer);
// 使用模块进行实例化等操作
});Module 对象解析WebAssembly.Module 对象类型签名为:var WebAssembly.Module: {
new (bytes: BufferSource): Module;
prototype: Module;
customSections(moduleObject: Module, sectionName: string): ArrayBuffer[];
exports(moduleObject: Module): ModuleExportDescriptor[];
imports(moduleObject: Module): ModuleImportDescriptor[];
}customSections 方法该方法用于获取模块中自定义的节(section)。其中参数:moduleObject 是一个 WebAssembly.Module 对象,sectionName 是一个字符串,表示要获取的自定义节的名称。该方法返回一个数组,这个数组包含了指定名称的自定义节的内容。例如:const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);
const customSections = WebAssembly.Module.customSections(module, 'custom_section_name');
console.log(customSections);exports 方法该方法用于获取模块中的导出项(exports)信息。其中参数:module 是一个 WebAssembly.Module 对象。该方法返回一个数组,包含了模块中所有导出项的信息。例如:const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);
const customSections = WebAssembly.Module.customSections(module, 'custom_section_name');
console.log(customSections);imports 方法该方法用于获取模块中的导入项(imports)信息。其中参数:module 是一个 WebAssembly.Module 对象。该方法返回一个数组,包含了模块中所有导入项的信息。例如:const response = await fetch('module.wasm');
// 将响应转换为 ArrayBuffer
const buffer = await response.arrayBuffer();
// 将 ArrayBuffer 编译成可执行的 WebAssembly 模块对象
const module = await WebAssembly.compile(buffer);
const imports = WebAssembly.Module.imports(module);
console.log(imports);2.3.2 Global() 构造函数 与 Global 对象WebAssembly.Global 对象的构造函数WebAssembly.Global() 构造函数 用于创建一个 WebAssembly.Global 实例对象。该构造函数的类型签名即下一节 Global 对象的类型签名 中的 new 方法的签名,这里不重复给出。Global 对象解析Global 对象的类型签名为:var WebAssembly.Global: {
new (descriptor: GlobalDescriptor, v?: any): Global;
prototype: Global;
}这个对象没有需要解释的其它方法。2.3.3 Instance() 构造函数 与 Instance 对象WebAssembly.Instance 对象的构造函数WebAssembly.Instance() 构造函数 用于创建一个 WebAssembly.Instance 实例对象。它接受一个 WebAssembly.Module 对象和一个可选的导入对象作为参数,并返回一个实例对象,用于调用 WebAssembly 模块中的函数和访问导出的功能。该构造函数的类型签名即下一节 Instance 对象的类型签名 中的 new 方法的签名,这里不重复给出。例如:fetch('module.wasm')
.then(response => response.arrayBuffer())
.then(buffer => {
// 创建 wasm 的 Module 对象实例
const module = new WebAssembly.Module(buffer);
// 创建 wasm 的 Instance 对象实例
const instance = new WebAssembly.Instance(module, { /* imports */ });
// 使用实例调用导出的函数等操作
});WebAssembly.Instance 对象Instance 对象的类型签名为:var WebAssembly.Instance: {
new (module: Module, importObject?: Imports): Instance;
prototype: Instance;
}这个对象没有需要解释的其它方法。2.3.4 Memory() 构造函数 与 Memory 对象WebAssembly.Memory 对象的构造函数WebAssembly.Memory() 构造函数用于创建一个 WebAssembly.Memory 实例对象。它接受一个描述内存大小的页数作为参数,并返回一个内存对象,用于 WebAssembly 模块中的内存访问操作。该构造函数的类型签名即下一节 Memory 对象的类型签名 中的 new 方法的签名,这里不重复给出。例如:const memory = new WebAssembly.Memory({ initial: 10, maximumMemory 实例对象的 buffer 属性是一个可调整大小的 ArrayBuffer ,其内存储的是 WebAssembly 实例 所访问内存的原始字节码。WebAssembly.Memory 对象Memory 对象的类型签名为:var WebAssembly.Memory: {
new (descriptor: MemoryDescriptor): Memory;
prototype: Memory;
}这个对象没有需要解释的其它方法。2.3.5 Table() 构造函数 与 Table 对象WebAssembly.Table 对象的构造函数WebAssembly.Table() 构造函数用于创建一个 WebAssembly.Table 实例对象。Table() 构造函数主要传入的是以下两个参数:initial:指定表的初始大小(以元素数量表示),默认为 0。element:指定表中每个元素的类型(函数引用类型),默认为 {element: “anyfunc”}。可以参考该对象类型签名及进行理解。该构造函数的类型签名即下一节 Table 对象的类型签名 中的 new 方法的签名,这里不重复给出。例如:// 创建一个初始大小为 10 的 WebAssembly 表,存储任意函数引用类型
const table = new WebAssembly.Table({ initial: 10 });
// 向表中添加函数引用
table.grow(2); // 扩展表的大小为 12
table.set(0, myFunction); // 将 myFunction 设置为索引为 0 的元素
table.set(1, anotherFunction); // 将 anotherFunction 设置为索引为 1 的元素
// 调用表中的函数
table.get(0)(); // 调用索引为 0 的函数WebAssembly.Table 对象Table 对象的类型签名为:type TableKind = "anyfunc" | "externref";
interface TableDescriptor {
element: TableKind;
initial: number;
maximum?: number;
}
var WebAssembly.Table: {
new (descriptor: TableDescriptor, value?: any): Table;
prototype: Table;
}TableKind 类型表示一个对象,表示表中每个元素的类型,可以是以下值之一:“anyfunc”: 任意函数引用类型“funcref”: 函数引用类型这个对象没有需要解释的其它方法。2.3.6 RuntimeError() 构造函数 与 RuntimeError 对象WebAssembly.RuntimeError 对象的构造函数WebAssembly.RuntimeError() 构造函数用于创建一个 WebAssembly.RuntimeError 实例对象。通过构造调用该对象,可以创建 WebAssembly 运行时错误(Runtime Error)。其中参数:message:一个可选的字符串,表示错误消息。例如:// 创建一个自定义的 WebAssembly 运行时错误
const error = new WebAssembly.RuntimeError("Custom runtime error occurred.");
throw error; // 抛出错误WebAssembly.RuntimeError 对象RuntimeError 对象用于在 WebAssembly 模块执行期间抛出自定义的运行时错误。通过 抛出自定义的 WebAssembly.RuntimeError,你可以在 WebAssembly 模块 的 执行期间捕获并处理特定的运行时错误情况。该对象的类型签名为:var WebAssembly.RuntimeError: {
(message?: string): RuntimeError;
new (message?: string): RuntimeError;
prototype: RuntimeError;
}通过抛出自定义的 WebAssembly.RuntimeError,你可以在 WebAssembly 模块的执行期间 捕获并处理特定的运行时错误情况。【注】:WebAssembly.RuntimeError 是一个构造函数,用于创建错误对象。它不是 JavaScript 中内置的异常类型,而是用于在 WebAssembly 环境中表示运行时错误的特定对象。这个对象没有需要解释的其它方法。2.3.7 CompileError() 构造函数 与 CompileError 对象WebAssembly.CompileError 对象的构造函数WebAssembly.CompileError() 构造函数 用来创建一个 WebAssembly.CompileError 实例对象,以实现在编译 WebAssembly 模块时抛出自定义的编译错误。其中参数:message:一个可选的字符串,表示错误消息。例如:// 创建一个自定义的 WebAssembly 编译错误
const error = new WebAssembly.CompileError("Custom compile error occurred.");
throw error; // 抛出错误WebAssembly.CompileError 对象CompileError 对象的类型签名为:var WebAssembly.CompileError: {
(message?: string): CompileError;
new (message?: string): CompileError;
prototype: CompileError;
}通过抛出 自定义的 WebAssembly.CompileError,你可以在编译 WebAssembly 模块时捕获并处理特定的编译错误情况。【注】:WebAssembly.CompileError不是 JavaScript 中内置的异常类型,而是用于在 WebAssembly 环境中表示编译错误的特定对象。2.3.8 LinkError() 构造函数 与 LinkError 对象WebAssembly.LinkError 对象的构造函数WebAssembly.LinkError() 构造函数 用于创建一个 WebAssembly.LinkError 实例对象,从而在链接 WebAssembly 模块时抛出自定义的链接错误。其中参数:message:一个可选的字符串,表示错误消息。例如:// 创建一个自定义的 WebAssembly 链接错误
const error = new WebAssembly.LinkError("Custom link error occurred.");
throw error; // 抛出错误WebAssembly.LinkError 对象LinkError 对象的类型签名为:var WebAssembly.LinkError: {
(message?: string): LinkError;
new (message?: string): LinkError;
prototype: LinkError;
}通过抛出自定义的 WebAssembly.LinkError,你可以在 链接 WebAssembly 模块时 捕获并处理特定的链接错误情况。【注】:WebAssembly.LinkError 是一个构造函数,用于创建错误对象。它不是 JavaScript 中内置的异常类型,而是用于在 WebAssembly 环境中表示链接错误的特定对象。这个对象没有需要解释的其它方法。2.3.9 Exception() 构造函数 与 Exception 对象WebAssembly 技术仍在不断发展和演进中,新的功能和规范也可能随着时间推移而推出。目前谷歌 Chrome 浏览器等浏览器已经支持该对象,不过不能确保所有就有的浏览器都被部分支持。笔者尚未实际使用过,因此先介绍过来,待日后获取更多信息再对本小节内容进行调整。该对象的 W3C 文档位置为:https://webassembly.github.io/exception-handling/js-api/#runtime-exceptionsWebAssembly.Exception 对象的构造函数WebAssembly.Exception() 构造函数 用于创建一个 WebAssembly.Exception 实例对象 其语法格式为:new Exception(tag, payload)
new Exception(tag, payload, options)其中:tag:用于定义 WebAssembly.Tag 中每个值的预期数据类型payload。payload:包含异常负载的一个或多个数据字段的数组。元素必须匹配 中相应元素的数据类型 tag。如果有效负载中的数据字段数量与其类型不匹配,TypeError 则会抛出异常。options(可选):具有以下可选字段的对象:traceStack:如果 Exception 可能有一个堆栈跟踪附加到它的 stack 属性,则为 true,否则为 false。默认情况下,这是 false (如果未提供 options 或 options.traceStack)。WebAssembly.Exception 对象它表示表示从 WebAssembly 抛出到 JavaScript 或从 JavaScript 抛出到 WebAssembly 异常处理程序的运行时异常。Exception 访问抛出的异常的参数需要用于创建 的同一 Tag。提供了方法来测试异常是否与特定标记匹配,以及通过索引获取特定值(如果异常与指定标记匹配)。当共享关联标签时,JavaScript 和其他客户端代码只能访问 WebAssembly 异常值,反之亦然(您不能只使用恰好定义相同数据类型的另一个标签)。如果没有匹配的标记,异常可以被捕获并重新抛出,但它们无法被检查。用法例如:// 创建标记并使用它来创建异常
const tag = new WebAssembly.Tag({ parameters: ["i32", "f32"] });
const exception = new WebAssembly.Exception(tag, [42, 42.3]);实例属性 stack返回异常的堆栈跟踪,或 undefined。WebAssembly.Exception.prototype.stack非标准:此功能是非标准的,不在标准轨道上。不要在面向 Web 的生产站点上使用它:它不适用于每个用户。实现之间也可能存在很大的不兼容性,并且行为可能会在未来发生变化。2.3.10 Tag() 构造函数 与 Tag 对象WebAssembly 技术仍在不断发展和演进中,新的功能和规范也可能随着时间推移而推出。目前谷歌 Chrome 浏览器等浏览器已经支持该对象,不过不能确保所有就有的浏览器都被部分支持。笔者尚未实际使用过,因此先介绍过来,待日后获取更多信息再对本小节内容进行调整。该对象的 W3C 文档位置为:https://webassembly.github.io/exception-handling/js-api/#dom-tag-tagWebAssembly.Tag 对象的构造函数WebAssembly.Tag() 构造函数 用于创建一个 WebAssembly.Tag 实例对象 其语法格式为:new WebAssembly.Tag(type)WebAssembly.Tag 对象type() 方法该方法返回为标签定义数据类型数组的对象(在其构造函数中设置)。例如:// 该例子为 MDN 给出,但是目前笔者测试的浏览器似乎并不支持。
const tagToImport = new WebAssembly.Tag({ parameters: ["i32", "f32"] });
console.log(tag.type());
// Console output:
// Object { parameters: (2) […] }
// parameters: Array [ "i32", "i64" ]
// <prototype>: Object { … }
将 Rust 程序编译为 WebAssembly 的知识与实践
Rust 笔记、WebAssembly将 Rust 程序编译为 WebAssembly 的知识与实践作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130859548上一节:《 开发环境搭建与 rust 工具介绍 》 | 下一节:《 如何加载和运行 WebAssembly 代码 》【介绍】:本文记叙如何将一个 Rust 语言编译成可执行的 WebAssembly 文件。目 录1. 概述1.1 什么是 WebAssembly1.2 WebAssembly 的特点1.2.1 高效1.2.2 安全1.2.3 开放1.3 本文受众与脉络1.3.1 关于受众1.3.2 脉络引导2. 快速入门 Rust-WebAssembly2.1 搭建 Rust 开发环境2.2 编写你的 Rust 代码2.3 在当前项目中添加 wasm_bindgen 和 wasm-pack 模块2.4 可能用到的各种各种工具Visual Studio 生成工具MinGWchocomsys2LLVM关于在类 Unix 系统上安装OpenSSLWin32OpenSSLPerl 运行时2.5 构建基于 wasm 的 npm 模块构建npm包关于构建的过程 wasm-pack 干的活3. 关于 wasm-pack 模块3.1 wasm-pack 是什么3.2 将 wasm-pack 模块添加到你的 Rust 项目3.3 安装 wasm-pack 的命令行工具3.3.1 在 Windows 上安装 wasm-pack CLI3.3.1 在 Linux 上安装 wasm-pack CLI3.3.3 wasm-pack CLI 的用法解析3.3.4 关于 wasm-pack 日志的补充3.3.5 改用 wasm-pack CLI 创建 Rust 项目4. 小结5. 关于编译出来的 pkg 项目your_package_bg.wasmyour_package_bg.wasm.d.tsyour_package.jsyour_package.d.tsREADME.mdpackage.json1. 概述1.1 什么是 WebAssemblyWebAssembly 是一种低级的类汇编语言,它是一种可以在现代的网络浏览器中运行的新的编码方式,并且可以接近原生的性能运行。依据官网的介绍,WebAssembly(缩写为 Wasm)是 基于堆栈的虚拟机 的 二进制指令格式。Wasm被设计为编程语言的可移植编译目标,支持 客户端和服务器应用程序 在 Web 上的部署。通过 WebAssembly 技术,我们可以为 Rust、C / C ++、Go 等多种语言提供一个编译目标,以便它们可以在 Web 上运行,并且是可以与 JavaScript 一起工作的。1.2 WebAssembly 的特点WebAssembly 官方对于该技术的优势归纳为以下几个方面:高效、安全、开放。1.2.1 高效Wasm堆栈机被设计为 以 大小 和 加载时间 有效的 二进制格式编码。WebAssembly 旨在利用各种平台上可用的通用硬件功能,以本机速度执行。1.2.2 安全WebAssembly描述了一个内存安全的沙盒执行环境,甚至可以在现有的JavaScript虚拟机中实现。当嵌入到web中时,WebAssembly将实施浏览器的同源和权限安全策略。1.2.3 开放WebAssembly 被设计成文本格式,用于调试、测试、实验、优化、学习、教学和手工编写程序。在网上查看Wasm模块的源代码时,将使用文本格式。同时,WebAssembly 被作为网络平台的一部分,其旨在维护 Web 的无版本、经过功能测试和向后兼容的特性。WebAssembly 模块将能够调入和调出 JavaScript 上下文,并通过可从 JavaScript 访问的相同 Web APIs 访问浏览器功能。WebAssembly 还支持 非web嵌入。1.3 本文受众与脉络1.3.1 关于受众本文针对 Web 前端 以及 NodeJS 或基于NodeJS 的桌面(如electron)或其它场景应用的开发人员进行讲解,假定已有 浏览器 或 NodeJS 的开发经验。1.3.2 脉络引导本文从零搭建一个 Rust 项目,在其中穿插讲解需要用到的一些知识,如 Rust 的 wask-pack 模块。然后在实际项目中简单将 Rust 项目编译好,分别讲述如何在 浏览器、NodeJS 中运行它。2. 快速入门 Rust-WebAssembly2.1 搭建 Rust 开发环境请参考博文 《开发环境搭建与 rust 工具介绍》,文本不再赘述。2.2 编写你的 Rust 代码新建 Rust 项目 hello-wasm:cargo new hello-wasm进入该项目:cd hello-wasm可以看到有以下目录和文件:在 src 目录下,有一个名为 main.rs 的文件。我们可以使用 VScode 或者 Vim 等程序编辑它,修改为我们自己的代码。vim src/main.rs由于我们目标是写一个用于 npm 的模块,这个自动生成的代码没有任何作用的。然后,我们另外编辑一个 src/lib.rs 文件:# 此处假定当前位于项目根目录
# 如果使用 VSCode,则使用命令 code src/lib.rs
vim src/lib.rs内容如下:extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
pub fn alert(s: &str);
}
#[wasm_bindgen]
pub fn greet(name: &str) {
alert(&format!("Hello, {}!", name));
}2.3 在当前项目中添加 wasm_bindgen 和 wasm-pack 模块前一节的代码中用到了 wasm_bindgen 模块,该模块用于 Rust 与 JavaScript 交互。另外还有 wasm-pack 模块用于构建 wasm,它们都需要单独在项目中安装。你可以直接使用 cargo 工具添加:cargo add wasm-packcargo add wasm_bindgen完成后,在项目配置文件中增加依赖项的记录:关于 添加 wasm-pack 模块 更详细的方法请参考 《3.2 将 wasm-pack 模块添加到你的 Rust 项目》 小节。然后编辑 Cargo.toml 的 lib 部分,以告诉 Rust 为我们的包建立一个 cdylib 版本。添加以下内容:[lib]
crate-type = ["cdylib", "rlib"]2.4 可能用到的各种各种工具在本文对应的实操,将会用到各种工具,由于不同的人习惯不一样,尤其是某些软件在官方不发布二进制文件仅仅发布源代码时,大家习惯于使用不同的社区构建版本。在这里博主我已经为读者提前下载好了各种工具,提供这些工具安装方式的介绍,这些工具都在我的资源上传区可以找到。rust 语言自生就需要各种依赖,如:python 3 or 2.7git一个C编译器 (为主机搭建时,cc就够了;交叉编译可能需要额外的编译器)curl (在Windows上不需要)pkg-config 如果您在Linux上编译并以Linux为目标libiconv (已经包含在基于Debian的发行版的glibc中)要构建Cargo,还需要OpenSSL(大多数Unix发行版上的 libssl-dev 或 openssl-devel)。如果从源代码构建LLVM,您将需要额外的工具:g++, clang++, 或LLVM文档中列出的 MSVC 版本ninja, 或 GNU make 3.81 或更新 (推荐Ninja,特别是在Windows上)cmake 3.13.4 或更新libstdc++-static 在一些Linux发行版上可能需要,比如Fedora和Ubuntu这一节记录相关的一些工具是如何安装的。Visual Studio 生成工具Rust的MSVC版本还需要安装Visual Studio 2017(或更高版本),以便 rustc 可以使用其链接器。最简单的方法就是通过 Visual Studio 安装。你可以访问 https://visualstudio.microsoft.com/zh-hans/thank-you-downloading-visual-studio/?sku=Community&channel=Release&version=VS2022&source=VSLandingPage&passive=false&cid=2030下载 Visual Studio 安装工具,然后选择安装 Visual Studio 生成工具安装:MinGWMinGW(Minimalist GNU on Windows)也就是 GCC 的 Windows 版本,其中GNU 编译器集合包括 C、C++、 Objective-C, Fortran, Ada、Go 和 D,以及这些语言的库 (libstdc++,…),而 GCC 最初是作为 GNU 操作系统的编译器编写的。针对 MSVC 的 Windows 平台(例如,您的目标三端in -msvc)要求cl.exe,可用并在 path 环境变量中。这通常出现在标准的Visual Studio 安装中,并且可以通过运行适当的开发人员工具 shell 来设置路径。面向 MinGW 的 Windows平台(例如-gnu中的目标三端)要求cc在 path 环境变量中可用。推荐使用 Win-builds 安装系统的 MinGW-w64 发行版。您也可以通过 MSYS2 获得它。确保安装与你的 rustc 安装相对应的适当架构。来自老 MinGW 项目的 GCC 仅与 32位 rust编译器 兼容。你可以在 https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/mingw-w64-v11.0.0.zip/download下载 MinGW 安装管理器:MinGW踩坑记录:安装构建rust后会提示没有 clang,其bin目录也确实没有这个文件。然后通过它选择包完成安装。你也可以自己在 https://sourceforge.net/projects/mingw-w64/files/mingw-w64/mingw-w64-release/ 上下载 MinGW-w64 的不同版本:而 MinGW-w64 官方网站的地址是:http://mingw-w64.org ,你可以在 https://sourceforge.net/projects/mingw-w64/ 寻找下载链接。choco在 Windows 上安装 mingw 的另外一个方式是使用 choco 包管理工具,你需要单独安装该工具。然后:choco install mingwmsys2或者使用 MSYS2 ,下载会相对容易些。其官网为:https://www.msys2.org/。你也可以访问 Mingw-w64 的官网 https://www.mingw-w64.org/ 安装其它版本。MSYS2踩坑记录:安装后会提示没有 clang,打开其目录,有一个clang64.exe和一个 clang32.exe。我尝试将它们改为 clang.exe 然后构建 rust,有弹框报错,不能处理文件。安装完成后,将你的安装目录添加到系统的 path 环境变量中,然后运行:msys2_shell -mingw64以开 mingw64 窗口,在该窗口中输入以下命令:# 更新软件包镜像(如果您全新安装了MSYS2,则可能需要)
pacman -Sy pacman-mirrors# 安装Rust所需的构建工具。如果您正在构建32位编译器,那么请将下面的“x86_64”替换为“i686”。
# 如果您已经安装了Git、Python或CMake,并且在 PATH 中,您可以从这个列表中删除它们。
# 请注意,不要**使用“msys2”子系统中的“python2”、“cmake”和“ninja”包,这一点很重要。众所周知,使用这些包时,构建会失败。
pacman -S git \
make \
diffutils \
tar \
mingw-w64-x86_64-python \
mingw-w64-x86_64-cmake \
mingw-w64-x86_64-gcc \
mingw-w64-x86_64-ninja这写都将安装到 mingw 的目录下,建议如果已经独立按照就不需要再安装了。安装完成以上这些后,在你的电脑上将可以导航到你的 Rust源代码,然后这样构建它:python x.py setup user && python x.py build && python x.py install如果内有安装,也不想使用 pacman 命令来安装。在 Windows也可以运行 winget 工具安装:winget install -e Python.Python.3
winget install -e Kitware.CMake
winget install -e Git.Git然后使用 pip 工具安装:pip install setuptools-rust
pip install cryptography
pip install paramikoLLVM或者使用 LLVM,这个我尝试时是好用的。LLVM-16.0.0-win64.exe:https://download.csdn.net/download/qq_28550263/87821395关于在类 Unix 系统上安装在 类 Unix 平台要求 cc 是 C 编译器。例如,这可以通过在 Linux 发行版上安装 cc/clang 和在 macOS 上安装 Xcode 来找到。OpenSSL在 Windows 上使用 wasm-pack 构建 WebAssembly 时,需要 OpenSSL 库。请确保你的系统上已安装 OpenSSL。a. 下载 OpenSSL:OpenSSL 官方不以二进制形式分发包,因此你无法下载官方版本的二进制安装包。可以先将源码下载过来:git clone https://github.com/openssl/openssl.git你需要参考:https://github.com/openssl/openssl/blob/master/NOTES-WINDOWS.md进行构建。社区提供了一些构建好的二进制版本,可以在社区的页面查看:https://wiki.openssl.org/index.php/Binaries(或者使用我准备地这个版本:https://download.csdn.net/download/qq_28550263/87847370 或https://download.csdn.net/download/qq_28550263/87821455)b. 安装 OpenSSL:构建的 OpenSSL 安装程序,并按照安装向导的指示完成安装过程。设置 OpenSSL 环境变量:如果已经安装了 OpenSSL,但wasm-pack无法找到它,您可以尝试手动设置 OpenSSL 的环境变量。a. 打开系统环境变量设置:在 Windows 11 上,点击 “开始” 按钮,搜索并打开 “编辑系统环境变量”。b. 点击 “环境变量” 按钮:在 “系统属性” 窗口中,点击 “环境变量” 按钮。c. 添加 OpenSSL 环境变量:在 “系统变量” 部分,点击 “新建” 按钮,并添加以下变量:变量名:OPENSSL_DIR变量值:OpenSSL 安装目录的路径(例如:D:\Program Files\OpenSSL)d. 点击 “确定” 保存设置,并关闭所有打开的窗口。重新运行 cargo install wasm-pack:在设置完 OpenSSL 环境变量后,重新运行 cargo install wasm-pack 命令,看是否仍然出现错误。这样做会将 OpenSSL 的路径信息传递给构建过程,以便成功编译和安装 wasm-pack。如果上述步骤仍然无法解决问题,请确保您按照正确的顺序执行了上述步骤,并且 OpenSSL 的安装路径正确设置。如果问题仍然存在,请尝试使用较新版本的 OpenSSL 或 wasm-pack,以确保软件版本的兼容性。如果问题仍然存在,请提供更多错误信息或运行命令时使用 RUST_BACKTRACE=1 环境变量,以便我能够更详细地了解问题并提供进一步的帮助。Win32OpenSSLWin32OpenSSL 也是一个社区预构建的 Windows 二进制版本:https://slproweb.com/products/Win32OpenSSL.html 上找到你需要的安装包版本,然后安装到指定文件夹。接着使用 Powershell 设置环境变量(依据你的安装位置修改):# 设置 OpenSSL-Win64 目录
[System.Environment]::SetEnvironmentVariable('OPENSSL_DIR','D:\Program Files\OpenSSL-Win64','User')
# 设置 OpenSSL-Win64 的 bin 目录
[System.Environment]::SetEnvironmentVariable('OPENSSL_LIB_DIR','D:\Program Files\OpenSSL-Win64\bin','User')
# 设置 OpenSSL-Win64 的 include 目录
[System.Environment]::SetEnvironmentVariable('OPENSSL_INCLUDE_DIR','D:\Program Files\OpenSSL-Win64\include','User')Perl 运行时某些模块在构建时期用到 Perl 运行时。我已经为读者朋友准备好了一个 Perl 运行时,其下载地址为:https://download.csdn.net/download/qq_28550263/87837427。下载好后可以自行安装。安装完成后,需要将 Perl 的二进制文件目录添加到系统的环境变量,以方便我们和程序日后进行访问或调用 Perl 解释器:比如我将 Strawberry 安装到 D 盘下,那么 Perl 的目录位于:D:\Strawberry\perl\bin。在 Windows 菜单中搜索 环境变量编辑器,将其单击打开:点击 “环境变量”:打开系统变量的 Path 变量名,点击新建,将我们刚刚的路径新增进去,然后保存退出。你可以使用以下命令查看是否安装成功:perl -v如果看到版本号等信息,说明你已经在你的计算机上成功地部署了 Perl:2.5 构建基于 wasm 的 npm 模块构建npm包【注意】:构建前你需要将 wasm-pack 的二进制目录注册到path环境变量。 windows 系统上如果一直使用的是安装到当前用户而非安装到系统, windows 系统上,wasm-pack 全局安装后,.wasm-pack位于 当前用户目录下的 AppData(通常为隐藏目录)下的Local目录下。 比如我的系统当前用户名为a2911,则对应 C:\Users\a2911\AppData\Local.wasm-pack。其中以 wasm-bindgen-cargo-install 开头的目录及其下的 bin 目录就是我们要添加到 Path 环境目录下的。比如我这里是 C:\Users\a2911\AppData\Local.wasm-pack\wasm-bindgen-cargo-install-0.2.86\bin。可能你对一些地方还不是很明白,不过作为快速上手,可以跟着本文先构建上面的 rust 项目为 npm 模块,这需要用到 wasm-pack CLI,可以参考 《3.3 安装 wasm-pack 的命令行工具》 进行安装。安装后,回到本项目的根目录,使用该脚手架的 build 命令进行构建,格式如下:wasm-pack build --target web第一次执行这个命令会需要很长一段时间,尤其是第一次执行时,需要下载一些相关模块,需要耐心等待。如图:一旦执行完成,可以在项目根目录下的 pkg 目录中找到为你生成的 npm模块,如图所示:这个目录就是一个包含 xxx.wasm、xxxx.wasm.d.ts、xxxx.js、xxxx.d.ts 的 一个 npm包:关于构建的过程 wasm-pack 干的活wasm-pack build 将做以下几件事:编译Rust:将你的 Rust 代码编译成 WebAssembly。生成JS接口:在编译好的 WebAssembly 代码基础上运行 wasm-bindgen,生成一个 JavaScript 文件将 WebAssembly 文件包装成一个模块以便 npm 能够识别它。移入新建pkg目录:创建一个 pkg 文件夹并将 JavaScript 文件和生成的 WebAssembly 代码移到其中。转换项目配置文件:读取你的 Cargo.toml 并生成相应的 package.json。复制 readme.md 文件:复制你的 README.md (如果有的话) 到文件夹中。结果:在 pkg 文件夹下得到了一个 npm 包。3. 关于 wasm-pack 模块3.1 wasm-pack 是什么依据该模块主页对自身的描述,该工具旨在成为 构建和使用 rust 生成的 WebAssembly 的 “一站式商店”(one-stop shop),你可以在 浏览器 或 Node.js 中使用 javascript 进行交互。wasm-pack 帮助您 构建 rust 生成的 WebAssembly 包,您可以将其发布到 npm 注册表,或者在您已经使用的工作流中与任何 JavaScript 包一起使用,如 Webpack。3.2 将 wasm-pack 模块添加到你的 Rust 项目和其它任意 Rust 模块一样,有两种方式添加该模块到你的项目中。第一种方式是直接使用 cargo 包管理工具:cargo add wasm-pack或者使用第二种方式,编辑项目的 Cargo.toml 文件,在模块依赖项处添加该行:wasm-pack = "0.11.1"【注意】:此项目需要 Rust 1.30.0 或更高版本。其中右侧双引号中的是该模块的版本号,本文写作时最新的版本号为"0.11.1",读者可以指定自己尝试时的最新版本号。3.3 安装 wasm-pack 的命令行工具wasm-pack 模块提供了一个命令行工具,你需要手动安装它。3.3.1 在 Windows 上安装 wasm-pack CLI你可以直接下载该初始化工具:https://github.com/rustwasm/wasm-pack/releases/download/v0.11.1/wasm-pack-init.exe,这个工具仅支持 64 位系统。下载完成后运行,将显示一个命令行窗口并很快就能够完成,完成后退出即可。一旦安装完成,将可以在你的系统中使用 wasm-pack 命令。3.3.1 在 Linux 上安装 wasm-pack CLI如果你是Linux或者Linux的Windows子系统用户,请在您的终端中运行以下程序,然后按照屏幕上的说明安装wasm-pack:curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh3.3.3 wasm-pack CLI 的用法解析该命令的语法格式为:wasm-pack [FLAGS] [OPTIONS] <SUBCOMMAND>其中,FLAGS 表示标志。提供以下标志项:命令别名描述-h--help打印帮助信息-q--quiet不输出信息到控制台-V--version打印版本信息-v--verbose日志详细程度基于使用的v的数量其中,OPTIONS 表示选项。提供以下选项:选项描述--log-level <log-level>表示 wasm-pack 应记录的最大消息级别。 可能的值包括: info, warn, error。默认值为:info。其中,SUBCOMMAND 表示子命令。提供以下子命令:子命令描述build🏗️ 构建您的 npm 包!help打印该消息或给定子命令的帮助login👤 添加npm注册表用户帐户!(别名:adduser、add-user)new🐑 使用模板创建新项目pack🍱 为你的 npm包 创建一个 tar,但是不要发布!publish🎆 打包你的 npm包 并发布!3.3.4 关于 wasm-pack 日志的补充wasm-pack 模块在内部使用 env_logger 模块作为其日志器。要配置日志级别,请使用 RUST_LOG 环境变量。例如(powershell):仅仅在当前窗口中有效的设置:$env:RUST_LOG="info"长期保存到当前用户的环境变量设置:[System.Environment]::SetEnvironmentVariable('RUSTUP_LOG','info','User')3.3.5 改用 wasm-pack CLI 创建 Rust 项目在 《3.3.3 wasm-pack CLI 的用法解析》 小节中,我们介绍了 wasm-pack 提供的脚手架,其中有一个 new 命令也是可以用来创建 Rust 项目的。现在我们使用该项目来“实操”一下:wasm-pack new hello-wasm该命令创建了一个名为 hello-wasm 的项目,其中包含了以下子目录和文件:该使用 wasm-pack 创建的项目配置文件 Cargo.toml 初始内容为:[package]
name = "hello-wasm"
version = "0.1.0"
authors = ["your_user_name <xxxxxx+xxxxx@users.noreply.github.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2.63"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
wee_alloc = { version = "0.4.5", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.3.13"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"其中默认为我们安装了 wasm-bindgen 模块,该模块用于 促进 Wasm模块 和 JavaScript之间的高级交互。4. 小结要在将 Rust 程序编译为 WebAssembly(Wasm),您可以按照以下步骤进行操作:安装 Rust:首先,您需要安装 Rust 编程语言的工具链。您可以从 Rust 官方网站(https://www.rust-lang.org)下载并安装 Rust。安装 wasm-pack:wasm-pack 是一个用于打包和构建 WebAssembly 的工具。您可以使用 Cargo(Rust 的包管理器)安装 wasm-pack。在命令行中运行以下命令来安装 wasm-pack:cargo install wasm-pack创建 Rust 项目:在命令行中,进入您的 Rust 项目的根目录,并执行以下命令创建一个新的 Rust 项目:cargo new my_project进入项目目录:使用 cd 命令进入新创建的项目目录:cd my_project编写 Rust 代码:使用您喜欢的文本编辑器编写 Rust 代码。将您的 Rust 代码保存在 src 目录下的 .rs 文件中。配置 Cargo.toml:在项目的根目录中,打开 Cargo.toml 文件,并添加以下内容来配置您的项目以构建为 WebAssembly:[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"构建 WebAssembly:在命令行中,执行以下命令以构建 WebAssembly:wasm-pack build --scope xxxxx --target web这将使用 wasm-pack 将 Rust 项目构建为 WebAssembly 模块。生成的 WebAssembly 文件将位于 pkg 目录下。集成 WebAssembly 到 Web 项目:将生成的 WebAssembly 文件(.wasm 和 .js 文件)复制到您的 Web 项目中,并通过 JavaScript 脚本加载和使用 WebAssembly。这些步骤将帮助您在 Windows 11 上将 Rust 程序编译为 WebAssembly。请注意,要成功编译为 WebAssembly,您的 Rust 代码和相关依赖库需要与 WebAssembly 目标兼容。在开发过程中,您可能还需要学习和了解 wasm-bindgen,它是一个用于在 Rust 和 JavaScript 之间进行交互的工具库。5. 关于编译出来的 pkg 项目在运行 wasm-pack build 后,生成的 pkg 目录是用于存放 WebAssembly 包(Wasm 包)及其相关文件的目录。这个目录中的文件用于在 Web 环境中使用和调用生成的 WebAssembly 模块。先前我们已经展示了该项目的结构:your_package_bg.wasm这是编译生成的 WebAssembly 模块文件,其中包含了你的 Rust 代码编译后的机器码。它是使用 Rust 编写的代码的编译结果,可以在 Web 环境中加载和执行。your_package_bg.wasm.d.ts这是一个 TypeScript 类型定义文件,用于提供与 your_package_bg.wasm 文件交互的类型信息。它包含了与 WebAssembly 模块中导出函数的类型定义,以便在 TypeScript 代码中正确使用这些函数。这些文件的结合使用,可以使你在 Web 环境中方便地使用 Rust 编写的 WebAssembly 模块。你可以使用 your_package.js 文件作为入口点,在你的 JavaScript 或 TypeScript 代码中调用和使用 WebAssembly 模块中的函数和对象。比如在我们的示例中,该文件的内容为:/* tslint:disable */
/* eslint-disable */
export const memory: WebAssembly.Memory;
export function greet(a: number, b: number): void;
export function __wbindgen_malloc(a: number): number;
export function __wbindgen_realloc(a: number, b: number, c: number): number;your_package.js这是与 WebAssembly 模块交互的 JavaScript 模块。它提供了与 WebAssembly 模块通信的接口,包括导入和导出函数,以及其他必要的功能。该文件提供了一个高级的 JavaScript API,使得在网页中可以方便地使用 WebAssembly 模块。比如在我们的示例中,该文件的内容为:let wasm;
const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); };
let cachedUint8Memory0 = null;
function getUint8Memory0() {
if (cachedUint8Memory0 === null || cachedUint8Memory0.byteLength === 0) {
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachedUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
ptr = ptr >>> 0;
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length) >>> 0;
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len) >>> 0;
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3) >>> 0;
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
/**
* @param {string} name
*/
export function greet(name) {
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
wasm.greet(ptr0, len0);
}
async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
function __wbg_get_imports() {
const imports = {};
imports.wbg = {};
imports.wbg.__wbg_alert_8755b7883b6ce0ef = function(arg0, arg1) {
alert(getStringFromWasm0(arg0, arg1));
};
return imports;
}
function __wbg_init_memory(imports, maybe_memory) {
}
function __wbg_finalize_init(instance, module) {
wasm = instance.exports;
__wbg_init.__wbindgen_wasm_module = module;
cachedUint8Memory0 = null;
return wasm;
}
function initSync(module) {
if (wasm !== undefined) return wasm;
const imports = __wbg_get_imports();
__wbg_init_memory(imports);
if (!(module instanceof WebAssembly.Module)) {
module = new WebAssembly.Module(module);
}
const instance = new WebAssembly.Instance(module, imports);
return __wbg_finalize_init(instance, module);
}
async function __wbg_init(input) {
if (wasm !== undefined) return wasm;
if (typeof input === 'undefined') {
input = new URL('hello_wasm_bg.wasm', import.meta.url);
}
const imports = __wbg_get_imports();
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
__wbg_init_memory(imports);
const { instance, module } = await __wbg_load(await input, imports);
return __wbg_finalize_init(instance, module);
}
export { initSync }
export default __wbg_init;your_package.d.ts这是 TypeScript 类型定义文件,用于提供与 WebAssembly 模块交互的类型信息。它定义了与 JavaScript 模块中的函数和对象进行交互时使用的类型签名,以提供静态类型检查和类型提示的支持。比如在我们的示例中,该文件的内容为:/* tslint:disable */
/* eslint-disable */
/**
* @param {string} name
*/
export function greet(name: string): void;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly greet: (a: number, b: number) => void;
readonly __wbindgen_malloc: (a: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
}
export type SyncInitInput = BufferSource | WebAssembly.Module;
/**
* Instantiates the given `module`, which can either be bytes or
* a precompiled `WebAssembly.Module`.
*
* @param {SyncInitInput} module
*
* @returns {InitOutput}
*/
export function initSync(module: SyncInitInput): InitOutput;
/**
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
* for everything else, calls `WebAssembly.instantiate` directly.
*
* @param {InitInput | Promise<InitInput>} module_or_path
*
* @returns {Promise<InitOutput>}
*/
export default function __wbg_init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;README.md这里再做一些补充说明。这里的 README.md 文件是 wasm-pack 自动地帮我们复制过来地,这就意味着我们只需要再原始的 Rust 项目中写好该模块的介绍。package.json这个用于 NodeJS 的项目配置文件是再原始 Rsut 项目配置文件 cargo.toml 的基础上进行生成的,并与之在一些基本信息上保持一致,比如相同的版本号,等等。
Rust 笔记Rust 语言中的常量与变量
Rust 笔记Rust 语言中的常量与变量作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130875912【摘要】:本文介绍 Rust 语言中的常量与变量。上一节:《 暂无》 | 下一节:《 Rust 语言中的的原始类型(Primitive Types) 》目 录1. 概述:常量与变量的概念1.1 常量 的概念1.2 变量 的概念2. Rust 中的常量2.1 定义常量2.2 Rsut 中常量的若干规则3. Rust 中的变量3.1 定义变量3.2 “不可变”的变量3.2.1 违背常理的变量3.2.2 不可变背后的考虑关于 数据竞争 的概念不可变的其它优势3.3 可变变量 与 mut 关键字1. 概述:常量与变量的概念这一小节主要是为方便刚刚学编程的小白读者准备的。在计算机编程中常量和变量都是用于存储数据的标识符,它们用于表示在程序执行过程中可能发生变化的值。常量和变量的区别在于它们的赋值后的行为:即是否可以再改变。1.1 常量 的概念常量 是一个在程序执行期间值不会改变的标识符。常量通常用于表示在程序中固定不变的值,如数学常数、配置参数等。一旦常量被赋值,就无法再修改其值。1.2 变量 的概念变量 是一个在程序执行期间其值可以改变的标识符。变量通常用于表示在程序中需要存储、修改或计算的值。变量的值可以在程序的不同位置进行修改,允许根据需要动态调整存储的数据。2. Rust 中的常量2.1 定义常量在 Rust 中,常量使用 const 关键字进行定义。常量的值在编译时就确定,并且不允许在程序执行期间更改。在 Rust 中定义的常量在 整个程序的作用域内 都有效。例如:const MAX_COUNT: u32 = 100_000;上面的这个常量的命名是遵循 Rust 语言常量命名规范的命名方式。在 Rust 语言中,常量的命名规范通常使用全大写字母,单词之间使用 下划线 (_) 进行分隔,这与 Python 语言中命名一个不在代码中改变的量的规范很像。不过 Python 中并没有真正意义上的常量,这个“不会改变”只存在与编程者头脑的契约中,而如果对 Rsut 中的常量进行修改,是回导致异常的。2.2 Rsut 中常量的若干规则常量的 类型 必须在 声明 时就明确指定,并且常量的值必须是一个编译时常量(在编译时可以确定其值)。这意味着不能将函数调用的结果或运行时计算的值赋给常量。常量在 Rust 中的使用方式与变量类似,可以在表达式中使用常量,传递给函数,作为模式匹配的一部分等。3. Rust 中的变量3.1 定义变量在 Rust 中,变量使用 let 关键字进行定义。变量的值可以根据需要在程序执行过程中进行修改。例如:fn main() {
let name = "Jack Lee";
let age: u32 = 27;
println!("姓名: {}", name);
println!("年龄: {}", age);
}输出结果为:姓名: Jack Lee
年龄: 273.2 “不可变”的变量3.2.1 违背常理的变量你觉得你定义的变量可以修改吗?看到本小节以这样的方式开头可能回让你有点以外——嗯,甚至有点 不安 和 疑惑:既然是变量,那还问什么是否可以修改吗。想到这里,你不妨试试修改并运行一下你的代码:fn main() {
let name = "Jack Lee";
let age: u32 = 27;
age = age-1;
println!("姓名: {}", name);
println!("年龄: {}", age);
}似乎有情况发生了,你看到的是下面这样的报错:error[E0384]: cannot assign twice to immutable variable `age`
--> src\main.rs:5:5
|
3 | let age: u32 = 27;
| ---
| |
| first assignment to `age`
| help: consider making this binding mutable: `mut age`
4 |
5 | age = age-1;
| ^^^^^^^^^^^ cannot assign twice to immutable variable
For more information about this error, try `rustc --explain E0384`.
error: could not compile `use-reg` due to previous error依据提示你可以查询一下 E0384:rustc --explain E0384你会看到:An immutable variable was reassigned.
Erroneous code example:
fn main() {
let x = 3;
x = 5; // error, reassignment of immutable variable
}
By default, variables in Rust are immutable. To fix this error, add the keyword
`mut` after the keyword `let` when declaring the variable. For example:
fn main() {
let mut x = 3;
x = 5;
}上面清楚地告诉你:一个不可变变量被重新分配!(An immutable variable was reassigned.)3.2.2 不可变背后的考虑是的!——虽然被叫做变量。但是默认情况下,Rust中的变量是不可变的。为什么 Rust 要设计出这种违背常理的逻辑呢?实际上将变量声明为不可变的,其目的在于 提供更强的安全性和防止潜在的错误。这种设计选择是 Rust 语言为了避免一类常见的编程错误,即 数据竞争(data races)。关于 数据竞争 的概念数据竞争 是指 多个线程 同时访问 共享数据,并且至少有一个线程试图写入该数据的情况。在并发编程中,数据竞争是非常危险的,因为它会导致不确定的行为、不可预测的结果以及安全漏洞。不可变的其它优势此外,Rust 语言中将变量默认为不可变还有以下优势:易于推理:不可变变量可以帮助程序员更容易地跟踪变量的状态和使用。由于不可变变量的值在绑定后不会发生改变,可以更加自信地推理代码的行为。预防错误:Rust 通过将变量默认声明为不可变的,鼓励程序员在编写代码时更加谨慎地处理数据共享和并发访问。当变量是不可变的时候,编译器可以进行更多的静态分析和优化,以确保并发访问的安全性。在编译时,Rust 编译器会检查对不可变变量的修改尝试,并在发现错误时发出错误信息。这可以帮助早期发现潜在的错误,减少调试和维护的难度。提高可读性:通过将变量声明为不可变的,代码的意图更加清晰明了。其他开发人员可以更容易地理解和维护你的代码,减少潜在的错误。3.3 可变变量 与 mut 关键字既然我们定义变量,那么就希望能够在某些时候改变它们。如果变量完完全全不可变,那就只需要常量好了。因此 Rust 也提供了 mut 关键字,使我们能够 显式地 将一个变量声明为 可变变量 以满足 修改数据 的需求。我们通过将 mut 关键字 放在变量名之前,可以指示编译器允许对该变量进行修改,例如,我们之前地例子中由于对不可变变量 age 做了修改导致于程序报错,现在只需为变量 age 加上 mute 关键字就能让程序正常运行:fn main() {
let name = "Jack Lee";
let mut age: u32 = 27;
age = age-1;
println!("姓名: {}", name);
println!("年龄: {}", age);
}其输出结果为:姓名: Jack Lee
年龄: 26这个例子表名,本文作者 Jack Lee 由于生活很开心,越活越年轻,又年轻了一岁😊。
Rust 笔记、设计模式发布订阅模式及其 在 Rust 语言中的使用
Rust 笔记、设计模式发布订阅模式及其 在 Rust 语言中的使用作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130877457【介绍】:本文介绍发布订阅模式的相关思想,以及第三方模块 EventEmitter 的使用。推荐阅读:《发布订阅模式原理及其应用(多种语言实现)》 这篇是我早前的博客,里面使用了 Powershell、Dart、Python、TypeScript 讲解或实现了一个 EventEmitter 对象。但是当时还没有考虑使用 Rust。本文多数内容直接来源于该博文,主要是将语言替换成了 Rust。上一节:《 Rust 文件 I/O 的使用 》 | 下一节:《 有限状态机原理及其在Rust 语言中的应用 》目 录1. 引例:从我的一个经历说起1.1 从 订阅 到 发布1.2 如果我不想继续订阅了2. 发布-订阅 的 实践、应用、思考2.1 实践:用 Rust 来复现上面的场景3. 通用型发布者对象的改进3.1 从 Subscriber 的服务员 到 事件的发布者3.2 一个比较初步的功能增强4. 使用现成的第三方模块:EventEmitter4.1 EventEmitter 的安装4.2 在你的项目中使用 EventEmitter4.3 EventEmitter 实例上的方法4.3.1 set_max_listeners 方法4.3.2 set_max_listeners 方法4.3.3 on 方法4.3.4 add_listener 方法4.3.5 off 方法4.3.6 remove_listener 方法4.3.7 emit 方法4.3.8 remove_all_listeners 方法4.3.9 prepend_listener 方法4.3.10 listeners 方法4.3.11 listener_count 方法1. 引例:从我的一个经历说起1.1 从 订阅 到 发布记得一九年的时候我刚刚来到深圳工作,众所周知那时候还没有爆发 新冠疫情,身边的同事们组队去香港购物是常有的事情。但是那会儿我还没有办理港澳通行证,于是年底回老家的时候去当地政务中心办理了。办证是需要时间的,万万没想到的是二零年春节前夕——新冠疫情爆发了。当我回到深圳后的某一天接到老家政务中心的电话,通知我由于疫情的原因,通信证的办理已经被暂停了并且什么时候恢复办理还不能确定,如果愿意等待,则需要到恢复办证的时候,再通知我们。—— 这就是一个 发布-订阅模式的典型例子。发布-订阅 模式 模式中的多方可以分为两类,一类是消息的 发布者,另外一类是消息的 订阅者。在上面的案例中,政务中心的工作人员就是 发布者,当我表示愿意等到恢复通信证办理时,我就 向发布者订阅了 恢复办理的通知(消息),因此我时消息的 订阅者。这样有什么好处呢:对于我(订阅者)来说,不需要每隔几天就打电话到政务服务中心(发布者)去询问是否恢复办理的消息;对于政务服务中心(发布者)同样也不需要每天回答相同的问题——毕竟何时恢复办理他们也不能确定。一旦恢复办理,政务服务中心(发布者)可以一次性地通知所有和我一样地广大订阅者。看到了吗——相比于我们去轮询以获取消息,改用发布-订阅 模式 同时节省了我们双方地时间!多么棒地思想!——运用于程序设计中岂不秒哉?1.2 如果我不想继续订阅了有一种情况也是非常常见的,那就是我不愿意继续等待消息了,也有可能是这个消息对我来说已经不重要了。这时我不再希望继续收到来自发布者的恢复办理通知,那就需要 退订。还记得吗——当我们订阅的时候,是将我们的订阅意愿登记在发布者那边的,这样就能实现发布者在适当的时候通过查询 所有的登记记录 然后逐一通知。因此如果一旦有用户需要退订,其实很简单,只需要订阅者在他们所登记订阅的“订阅者登陆表”中将订阅信息删除掉即可,这样下一次广播通知的时候就不会再将消息发送给退订的用户。2. 发布-订阅 的 实践、应用、思考2.1 实践:用 Rust 来复现上面的场景如果现在你好像明白 发布-订阅模式 的基本思想了——那么就请成热打铁,跟着我用程序来模拟一下证件办理的情景。use std::collections::HashSet;
#[derive(Eq, Hash, PartialEq, Clone)]
struct Subscriber {
name: String,
}
struct Publisher {
subscribers: HashSet<Subscriber>,
name: String,
}
impl Publisher {
fn new(name: &str) -> Publisher {
Publisher {
subscribers: HashSet::new(),
name: name.to_string(),
}
}
fn add_subscriber(&mut self, subscriber: &Subscriber) {
self.subscribers.insert(subscriber.clone());
}
fn remove_subscriber(&mut self, subscriber: &Subscriber) {
self.subscribers.remove(subscriber);
println!("\n=> {} 已取消订阅。\n", subscriber.name);
}
fn notify_all(&self, arg: &str) {
for subscriber in &self.subscribers {
subscriber.notify(self, arg);
}
}
}
impl Subscriber {
fn new(name: &str) -> Subscriber {
Subscriber {
name: name.to_string(),
}
}
fn notify(&self, publisher: &Publisher, arg: &str) {
println!(
"\"{}\"(订阅者) 收到的通知来自 \"{}\"(发布者)的通知: {}",
self.name, publisher.name, arg
);
}
}
fn main() {
let mut publisher = Publisher::new("政务服务中心");
let jack_lee = Subscriber::new("jackLee");
let jack_ma = Subscriber::new("jackMa");
publisher.add_subscriber(&jack_lee);
publisher.add_subscriber(&jack_ma);
println!("------- 第一次发布消息 -------");
publisher.notify_all("[通知] 恢复证件办理!");
// 用户 jackMa 取消订阅
publisher.remove_subscriber(&jack_ma);
println!("------- 第二次发布消息 -------");
publisher.notify_all("[通知] 恢复证件办理!");
}运行该脚本,输出如下:------- 第一次发布消息 -------
"jackLee"(订阅者) 收到的通知来自 "政务服务中心"(发布者)的通知: [通知] 恢复证件办理!
"jackMa"(订阅者) 收到的通知来自 "政务服务中心"(发布者)的通知: [通 知] 恢复证件办理!
=> jackMa 已取消订阅。
------- 第二次发布消息 -------
"jackLee"(订阅者) 收到的通知来自 "政务服务中心"(发布者)的通知: [通知] 恢复证件办理!可以看到订阅者有两个订阅者实例:jackLee(本人)、jackMa(可能是阿里出来的)共同订阅了证件办理信息。政务服务中心(发布者)第一次发布恢复通知的时候,jackLee 和 jackMa 这两个同学都订阅了消息,因此都受到了来自该中心的通知。后来,jackMa 可能由于已经派小弟火速前往该中心取走了他的证件不需要继续订阅了,于是该中心的工作人(发布者)员调用publisher.remove_subscriber(jackMa) 从该中心的订阅者记录表中移除了 jackMa 的订阅记录。于是,到了该中心第二次发布消息的时候,jackMa 已经不会再收到恢复证件办理消息,而 jackLee 还可以接收到恢复证件办理的消息。3. 通用型发布者对象的改进3.1 从 Subscriber 的服务员 到 事件的发布者在阅读本小节前请读者先自己尝试回答这个问题:Subscriber 类真的有必要实现吗?在我们上面的代码中,Subscriber 类 实现了几乎唯一一个有用的方法:update,它的作用却是给 Publisher 类的 notifyAll 方法进行调用。从现实生活中给一个解释:notifyAll 是消息的发布这发布消息的工具,update 是订阅用户接受到的新的定制化消息,比如同样是订阅了售房信息,但是由于不同类别的购房者订阅时所选定的楼层、大小等参数不一样,则这些不同订阅者接收到的发布结果不一样——也就表面原始的消息需要为不同的订阅者做一些 定制化 处理。在之前的代码实现中,这个消息的定制化工作就是使用 Subscriber 类的 update 方法实现的。很显然,上表面的代码要真正实现定制化,往往不仅是参数值的不同,可能对参数的处理也不一样。因此仅仅依赖参数data是不合理的。因此我们大概是需要写多个仅仅 update 方法的实现不同的 Subscriber 类——这不太好。略好一点的办法是,让 update 方法接受的不是单纯的数据 data,而是一个 回调函数 传入 update 方法中。先不着急修改我们的代码。对于发布者来说,似乎可以提供 更加周到 的服务——直接登记好订阅着的定制化需求处理方式,使用订阅者要求的处理方式处理好定制的消息后,直接告诉订阅者。——因此 update 这个接受表示用户定制化需求处理方式的方法可以直接合并到 发布者那边。于是 Subscriber 类 就不需要了,现在我们只需要更新一下我们的 Publisher。更新的思路是这样的:添加订阅者时(Publisher.addSubscriber)不仅需要记录订阅者名字,还要记录一个对应的响应函数用以消息发布后给订阅者提供定制化服务。从 Publisher 看,需要登记的内容又多了一些。不过好在 订阅者名称(认为是唯一标识符)和 与之对于的服务(回调函数),是对应的关系,既可以一对一,也可以一对多(表示这个订阅者需要多个定制化服务)。因此我们将 Publisher 的 “记录本”改成下面的类型:HashMap<String, Vec<EventCallback>>这个映射(Map)的 key 就表示 订阅者名称,而value 部分是一组函数,表示该订阅者需要的各种服务。另外,到了这里,对于 Publisher来说,添加订阅者就转化为了 为订阅者订阅各种定制化服务 。同时反过来看,对于某个具体的订阅者 Subscriber,一旦它的服务定制数组 (Function[])为空数组,表明他已经没有任何订阅,也不再需要接收发布者的任何消息了。因此先前我们使用的方法名addSubscriber 不适用了,从含以上换成 addListener 似乎更加合适。为什么呢? 我们接下来对此做进一步说明。一直以来,我们聚焦点都在于 发布者 和 订阅者,而忽略了 引起发布者发布的事件。 这个方法接受两个参数,一个是用户名,一个是为用户新增的回调。同时必须指出的是,这个 回调 往往是需要再其调用时接受一些数据的,比如由发布者发布的某些原始数据,他们就像是时时刻刻地 监听着、守候着 发布者 发布一个事件,一旦这个 事件/消息 被发布,就 完成消息发布后为 订阅者 所提供地服务。换一下思路,我们接着把聚焦点转移到 事件 上来。其实从现实中看,同一个事件发生,可能意味着可能需 要干很多事,既可以 服务更多的订阅者,也可以干其它任何的工作——我们一味地想着在发布者处登记订阅者的id然后完成订阅者的需求,那么 没有区别为何事件而需要去发布这些消息!更好的做法是 不再记录订阅者,而是记录为什么要发布消息给订阅者——也就是记录 事件。这样我们就可以在同一个事件发生的时候,通过一系列的属于该事件函数(可能一个或多个回调函数服务于同一个订阅者),完成该事件的响应,也就是回调函数们。从这个意义上看,我们所关注所谓的 订阅者 可以看作 一个事件发布后,发布者需要调用的一组函数。而所谓 发布,实际上就是调用这组函数以 完成事件(的回调)。因此我们接下来该用 listener 表示监听事件以待执行的回调函数, event 表示事件名, emit 表示这个事件发生后需要由发布者调用函数的过程。至此,我们的 Publisher 从一个 Subscriber 的服务员 转型成为了职业 事件的管理者,不妨给它改个名——EventEmitter。现在我们实现一个最基础的 EventEmitter 对象:use std::collections::HashMap;
type EventCallback = Box<dyn Fn()>;
struct EventEmitter {
/// 事件-监听器数组容器
_events: HashMap<String, Vec<EventCallback>>,
}
impl EventEmitter {
fn new() -> EventEmitter {
EventEmitter {
events: HashMap::new(),
}
}
/// 添加事件监听器,监听器是一个回调函数,表示用户订阅的具体服务
fn add_listener(&mut self, event: &str, callback: EventCallback) {
let callbacks = self._events.entry(event.to_string()).or_insert(Vec::new());
callbacks.push(callback);
}
/// 移除事件监听器:相当于用户取消订阅
fn remove_listener(&mut self, event: &str, callback: &EventCallback) {
if let Some(callbacks) = self._events.get_mut(event) {
callbacks.retain(|cb| cb != callback);
}
}
/// 触发事件:相当于发布消息或服务,也就是事件发生时,将订阅者订阅的服务一一为订阅者执行
fn emit(&self, event: &str) {
if let Some(callbacks) = self._events.get(event) {
for callback in callbacks {
callback();
}
}
}
}3.2 一个比较初步的功能增强不过很多时候我们还不满足于此,比如能够限制监听器的数量。从现实生活中打个比方,就像我们只服务一定数量的客户,一旦订满,由于资源有限,不再接收其它订阅。更贴切实际地说,就像节假日你去旅游景区地酒店订房间,对于酒店来说,一旦所有房间都预定满了,就不再接收新的订阅了——除非,有已经订阅地客人退订了它们先前已经预定地房间。实现这样一个功能,只需要一个变量 _max_listeners 作为最大监听器数量的控制变量。在外部相应的我们需要允许用户修改和读取该变量的值,因此还要提供 set_max_listeners 和 get_max_listeners 两个方法。use std::collections::HashMap;
use std::sync::{Arc, Mutex};
type EventCallback = Arc<dyn Fn() + Send + Sync>;
pub struct EventEmitter {
_events: Mutex<HashMap<String, Vec<EventCallback>>>,
_max_listeners: usize,
}
impl EventEmitter {
pub fn new() -> Self {
EventEmitter {
_events: Mutex::new(HashMap::new()),
_max_listeners: usize::MAX,
}
}
/// 设置最大监听器数量
/// Set the maximum number of listeners
pub fn set_max_listeners(&mut self, max_listeners: usize) {
self._max_listeners = max_listeners;
}
/// 获取最大监听器数量
pub fn get_max_listeners(&self) -> usize {
self._max_listeners
}
/// 添加事件监听器
pub fn add_listener(&self, event: &str, callback: EventCallback) {
let mut events = self._events.lock().unwrap();
let callbacks = events.entry(event.to_string()).or_insert(Vec::new());
callbacks.push(callback);
}
/// 移除事件监听器
pub fn remove_listener(&self, event: &str, callback: &EventCallback) {
let mut events = self._events.lock().unwrap();
if let Some(callbacks) = events.get_mut(event) {
callbacks.retain(|cb| !Arc::ptr_eq(cb, callback));
}
}
/// 触发事件
pub fn emit(&self, event: &str) {
let events = self._events.lock().unwrap();
if let Some(callbacks) = events.get(event) {
for callback in callbacks {
let callback_clone = callback.clone();
// Spawn a new thread to run each callback asynchronously
std::thread::spawn(move || {
(*callback_clone)();
});
}
}
}
}4. 使用现成的第三方模块:EventEmitter4.1 EventEmitter 的安装你可以直接使用 cargo 包管理器安装 EventEmitter:cargo add EventEmitter4.2 在你的项目中使用 EventEmitter以下是一段包括引入 EventEmitter 和使用的例子:use std::sync::{Arc};
use EventEmitter::EventEmitter;
fn main() {
let emitter = EventEmitter::new();
let callback1 = Arc::new(|| println!("[event1 emitted]: The first callback of event1 has been called."));
let callback2 = Arc::new(|| println!("[event1 emitted]: The second callback of event1 has been called."));
let callback3 = Arc::new(|| println!("[event2 emitted]: The only one callbask of event2 has been called."));
// Add event listener
emitter.on("event1", callback1);
emitter.on("event1", callback2);
emitter.on("event2", callback3);
let ct1 = emitter.listener_count("event1");
let ct2 = emitter.listener_count("event2");
println!("Number of Listeners for event1 is: {ct1}, \nNumber of Listeners for event2 is: {ct2}");
emitter.emit("event1"); // Emit event1
emitter.emit("event2"); // Emit event1
}运行项目:cargo run可以看到控制台打印结果:Number of Listeners for event1 is: 2,
Number of Listeners for event2 is: 14.3 EventEmitter 实例上的方法4.3.1 set_max_listeners 方法pub fn set_max_listeners(&mut self, max_listeners: usize)设置最大监听器数量。4.3.2 set_max_listeners 方法pub fn get_max_listeners(&self) -> usize获取最大监听器数量。4.3.3 on 方法pub fn on(&self, event: &str, callback: Arc<dyn Fn() + Send + Sync>)添加事件监听器。4.3.4 add_listener 方法pub fn add_listener(&self, event: &str, callback: Arc<dyn Fn() + Send + Sync>)添加事件监听器,是 on 方法的别名。4.3.5 off 方法pub fn off(&self, event: &str, callback: &Arc<dyn Fn() + Send + Sync>)移除事件监听器。4.3.6 remove_listener 方法pub fn remove_listener(
&self,
event: &str,
callback: &Arc<dyn Fn() + Send + Sync>
)移除事件监听器,是 off 方法的别名。4.3.7 emit 方法pub fn emit(&self, event: &str)触发事件。触发相当于“发布-订阅”模式中的“发布”,一但某个事件被触发,该事件对应得所有监听器函数都会被执行。监听器就相当于“订阅者”。4.3.8 remove_all_listeners 方法pub fn remove_all_listeners(&self, event: &str)移除所有事件的所有监听器。4.3.9 prepend_listener 方法pub fn prepend_listener(
&self,
event: &str,
callback: Arc<dyn Fn() + Send + Sync>
)从指定事件监听器向量的前方插入新的监听器。该方法与使用 on、add_listener 方法添加新的监听器时,插入监听器向量的方向相反。4.3.10 listeners 方法pub fn listeners(&self, event: &str) -> Vec<Arc<dyn Fn() + Send + Sync>>获取指定事件的监听器。4.3.11 listener_count 方法pub fn listener_count(&self, event: &str) -> usize获取指定事件的监听器数量。
Rust 笔记Rust 语言中应用正则表达式
Rust 笔记Rust 语言中应用正则表达式作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343邮箱 :291148484@163.com本文地址:https://blog.csdn.net/qq_28550263/article/details/130876707【介绍】:本文讲解 正则表达式相关概念,以及如何在 Rust 语言中使用正则表达式。上一节:《 Rust 语言中的字符串 》 | 下一节:《 Rust 语言中哈希表(HashMap)及其使用 》目 录1. 概述1.1 正则表达式 和 regex 模块1.2 在 Rust 项目中配置 regex 模块2. 字符串规则描述符2.1 定位符:描述字符的边界2.2 限定符:描述重复匹配的次数2.3 字符描述2.4 管道符:“或”匹配逻辑2.5 转义字符:将特殊符号标识为普通字符2.6 分组3. 创建正则表达式对象的方法3.1 正则对象的字面量3.2 使用正则表达式字符串3.3 使用正则表达式字符串和编译选项3.4 使用正则表达式字符串和编译选项和错误处理4. 实现其它语言正则中匹配模式标志的功能4.1 关于匹配模式4.2 全局匹配4.3 忽略大小写匹配4.4 多行匹配模式4.5 允许 `.` 去匹配新的行4.6 内联匹配的组合4.7 视为 Unicode 码位序列5. Regex API 解析5.1 核心正则表达式方法5.1.1 new 方法描述5.1.2 is_match 方法描述例子5.1.3 find 方法描述例子5.4 find_iter 方法描述例子5.1.5 captures 方法描述例子5.1.6 captures_iter 方法描述例子5.1.7 split 方法描述例子5.1.8 splitn 方法描述例子5.1.9 replace 方法描述替换字符串语法例子5.1.10 replace_all 方法描述5.1.11 replacen 方法描述5.2 高级或“低级”搜索方法5.2.1 shortest_match 方法描述例子5.2.2 shortest_match_at 方法描述例子5.2.3 is_match_at 方法描述例子5.2.4 find_at 方法描述例子5.2.5 captures_at 方法描述例子5.2.6 captures_read 方法描述例子5.2.7 captures_read_at 方法描述例子5.3 辅助方法5.3.1 as_str 方法描述例子5.3.2 capture_names 方法描述5.3.3 captures_len 方法描述5.3.4 static_captures_len 方法描述例子5.3.5 static_captures_len 方法描述附录: 查询元字符表优先级(从上到下)内联匹配模式1. 概述1.1 正则表达式 和 regex 模块正则表达式用于描述各种复杂的字符串关系,使用正则表达式能够更加灵活便捷地处理字符串,它是使用单个字符串来描述、匹配一系列符合某个句法规则的字符串搜索模式。在 Rust 语言中,目前我们主要通过 regex 模块 来使用正则表达式。 regex 模块 是一个用于解析、编译和执行正则表达式的 Rust 库。依据该模块自己的介绍,它的语法类似于 Perl 风格的正则表达式,但是缺少一些特性,比如环视和反向引用。作为交换,所有搜索都根据正则表达式和搜索文本的大小以线性时间执行。很多语法和实现都是受RE2的启发。你可以在 https://crates.io 的 regex 模块主页中查看器最新的发布信息:https://crates.io/crates/regex,或者直接进入该模块的 Github 开源仓库查看:https://github.com/rust-lang/regex。1.2 在 Rust 项目中配置 regex 模块现在我们准备一个新的 rust 项目,来讲解如何配置使用 regex 模块:# 新建项目
cargo new use-regex接着,进入该项目,并安装regex:# 进入 use-regex 项目根目录
cd use-regex# 安装 regex 模块
cargo add regexTip:如果你的安装很慢,不妨参考博文 《Crate 国内源配置》 设置和使用 rust cargo 工具的国内源镜像进行安装。安装成功可,可以看到项目的Cargo.toml配置文件新增了一个 regex 的依赖项:2. 字符串规则描述符>点击此处可以直接查询元字符表2.1 定位符:描述字符的边界符号描述说明^匹配一个字符串的起始字符如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置。$匹配一个字符串的结尾字符如果多行标志被设置为 true,那么也匹配换行符前的位置。\b匹配一个单词的边界-\B匹配非单词边界相当于\b匹配的反集例如从一篇博客的博文中,提取所有的标题:use regex::Regex;
fn main() {
let markdown_text = "# Header 1\n## Header 2\n### Header 3\n#### Header 4\n##### Header 5\n###### Header 6";
let re = Regex::new(r"(?m)^(#{1,6})\s+(.+)$").unwrap();
for cap in re.captures_iter(markdown_text) {
let level = cap[1].len();
let header = cap[2].trim();
println!("标题等级:{};文本:\"{}\"", level, header);
}
}其输出结果为:标题等级:1;文本:"Header 1"
标题等级:2;文本:"Header 2"
标题等级:3;文本:"Header 3"
标题等级:4;文本:"Header 4"
标题等级:5;文本:"Header 5"
标题等级:6;文本:"Header 6"2.2 限定符:描述重复匹配的次数符号描述说明?匹配该限定符前的字符0或1次等价于 {0,1},如 colou?r 可以匹配colour和color+匹配该限定符前的字符1或多次等价于 {1,},如 hel+o可以匹配helo、hello、helllo、…*匹配该限定符前的字符0或多次等价于 {0,},如 hel*o可以匹配heo、helo、hello、helllo、…{n}匹配该限定符前的字符n次如 hel{2}o只可以匹配hello{n,}匹配该限定符前的字符最少n次如 hel{2,}o可以匹配hello、helllo、…{n,m}匹配该限定符前的字符最少n次,最多m次如 hel{2,3}o只可以匹配hello 和 helllo2.3 字符描述符号描述说明\d匹配任意数字\s匹配任意空白符\w匹配任意字母、数字、下划线、汉字等\D匹配任意非数字\S匹配任意非空白符\W匹配除了字母、数字、下划线、汉字以外的字符.匹配除了换行符以外的任意字符形式描述说明[A-Z]区间匹配,匹配字母表该区间所有大写字母如[C-F]匹配字符C、D、E、F[a-z]区间匹配,匹配字母表该区间所有小写字母如[c-f]匹配字符c、d、e、f[0-9]区间匹配,匹配该区间内的所有数字如[3-6]匹配字符3、4、5、6[ABCD]列表匹配,匹配[]中列出的所有字母如这里列出的A、B、C、D都会被匹配[^ABCD]列表排除,匹配除了[]中列出的字符外的所有字符如这里列出的A、B、C、D都会被排除而匹配其它字符例如,使用正则表达式提取文本中的数字和非数字分类返回:use regex::Regex;
use std::collections::HashMap;
// 定义一个函数,接收一个字符串参数,返回一个哈希表
fn classify_text(text: &str) -> HashMap<&str, Vec<&str>> {
// 定义一个正则表达式,匹配数字和非数字字符
let re = Regex::new(r"(\d+)|(\D+)").unwrap();
let mut map = HashMap::new();
// 遍历匹配结果
for cap in re.captures_iter(text) {
// 获取匹配到的数字或非数字字符
let value = cap.get(0).unwrap().as_str();
// 根据匹配到的数字或非数字字符,将其加入到对应的向量中
if cap.get(1).is_some() {
map.entry("数字").or_insert(Vec::new()).push(value);
} else {
map.entry("非数字").or_insert(Vec::new()).push(value);
}
}
map
}
fn main() {
let text = "abc123def456";
let result = classify_text(text);
println!("{:?}", result);
}运行项目:cargo run可以看到输出结果为:{"非数字": ["abc", "def"], "数字": ["123", "456"]}再如,匹配IPV4地址:use regex::Regex;
fn main() {
let re = Regex::new(r"[1-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[1-9]{1,3}").unwrap();
let text = "这是一段用于演示匹配的文本。192.168.0.1 是一个合法的 IP 地址,然而 900.300.700.600 不是.";
for cap in re.captures_iter(text) {
println!("匹配到合法的IP地址: {}", &cap[0]);
}
}然后在你的项目目录下运行命令:cargo run可以从输出结果中看到文本中的前一个地址被提取出来了:2.4 管道符:“或”匹配逻辑管道符就是一根竖线:|,再正则表达式中标识”或“。由于匹配方向时从左向右进行的,假如有两个正则表达式A和B,那么使用 A|B 匹配字符串时,只要前一个样式完全匹配成功,那么后一个就不再匹配。例如:use regex::Regex;
fn main() {
let re = Regex::new(r"hello|world").unwrap();
let text = "hello world";
for mat in re.find_iter(text) {
println!("{}", mat.as_str());
}
}输出为:hello
world2.5 转义字符:将特殊符号标识为普通字符在正则表达式中用于标识特殊含义的符号如.用于标识一个任意的非换行符字符,^标识起始字符,等等。但是我们希望匹配到这些字符本身也是经常遇到的情况,如IPV4地址使用.作为分割符。因此我们如果需要完整提取IPV4地址就需要表示.本身。由于直接使用点已经有了其它的含义,因此我们使用一个\号进行转义,即使用\.来表示点(.)。其它在正则中有特殊含义的符号也可以使用类似的方式。2.6 分组与数学计算中采用小括号()进行算式分组一样,正则模板也能够分组表达,目的是将某一部分正则模板作为一个整体表达,例如模板:use regex::Regex;
fn main() {
let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
let text = "当前的日期是 2023-05-28";
if let Some(captures) = re.captures(text) {
println!("年: {}", captures.get(1).unwrap().as_str());
println!("月: {}", captures.get(2).unwrap().as_str());
println!("日: {}", captures.get(3).unwrap().as_str());
}
}运行项目:cargo run可以从输出结果中看到从文本中分别取出了年、月、日:3. 创建正则表达式对象的方法3.1 正则对象的字面量Rust中的正则表达式字面量是由斜杠(/)包围的字符串。可以使用正则表达式的方法来匹配和搜索字符串。例如,下面的代码将匹配所有以字母“a”开头的单词:let re = Regex::new(r"\ba\w+").unwrap();这个正则表达式使用了单词边界(\b)和一个或多个字母数字字符(\w+)来匹配以字母“a”开头的单词。再比如使用正则表达式来替换字符串中的所有匹配项:let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
let date_replaced = re.replace_all("Today's date is 2022-01-01", "$2/$3/$1");这个正则表达式匹配日期格式“YYYY-MM-DD”,然后使用捕获组来重新排列日期格式为“MM/DD/YYYY”。3.2 使用正则表达式字符串例如:let re = Regex::new("^\\d{3}-\\d{2}-\\d{4}$").unwrap();3.3 使用正则表达式字符串和编译选项例如:let re = Regex::new_with_options("^\\d{3}-\\d{2}-\\d{4}$", RegexOptions::MULTILINE).unwrap();3.4 使用正则表达式字符串和编译选项和错误处理例如:let re = match Regex::new_with_options("^\\d{3}-\\d{2}-\\d{4}$", RegexOptions::MULTILINE) {
Ok(re) => re,
Err(err) => panic!("Failed to create regex: {}", err),
};4. 实现其它语言正则中匹配模式标志的功能4.1 关于匹配模式一些语言的正则表达式,如 JavaScript、Python 等等,可以使用一定的形式指定 匹配模式,比如 JavaScript 语言中的正则表达式可以在正则表达式字面量末尾附加以下模式符号,实现相关的匹配功能:符号描述说明g全局匹配找到所有的匹配,而不是在第一个匹配之后停止。i忽略大小写如果u标志也被启用,使用Unicode大小写折叠。m多行匹配将开始和结束字符(^ and $)视为在多行上工作。换句话说,匹配每一行的开头或结尾each line (由\n或者\r 分隔),而不仅仅是整个输入字符串的开头或结尾。s点号匹配所有字符允许. 去匹配新的行uunicode将 pattern 视为 Unicode 码位序列。 参考 二进制字符串ysticky,粘性匹配仅从目标字符串中此正则表达式的 lastIndex 属性指示的索引中匹配。不尝试从任何后续索引中匹配从目前笔者所使用过的所有编程看,个人最喜欢 JavaScript 提供的正则表达式用法,因为它用起来最方便。很遗憾的是,Rust 正则表达式没有类似的匹配模式写法,不过部分功能的实现,可以借用也有很多语言在用的 内联匹配模式 语法,如 C#。可以参考 内联匹配模式,以及正则对象提供的各种方法来实现。4.2 全局匹配我们先看一段使用 JavaScript 正则表达式的代码,对比使用全局匹配与否有什么区别:const str = 'hello1 world1, hello2 world2';
const reg1 = /hello/;
const reg2 = /hello/g;
console.log(str.match(reg1));
console.log(str.match(reg2));其输出为:[
'hello',
index: 0,
input: 'hello1 world1, hello2 world2',
groups: undefined
]
[ 'hello', 'hello' ]可看出,不使用全局匹配g,只匹配到第一个符合条件的字符串。而使用全局匹配g,匹配到所有符合条件的字符串。目前在 rust 这边会显得比较 笨拙 一些,需要使用不同的方式来实现。以查找字符串为例。如果我们不希望全局匹配,只要求第一个匹配结果时,我们可以使用 find 方法。例如下面这个例子中,我们提取第一个年月日:let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
let text = "第一个年月日:2021-01-01, 第二个年月日:2022-02-01, 第三个年月日:2023-03-01";
let res = re.find(text).unwrap();
println!("{}",res.as_str())其输出结果为:2021-01-01然而如果我们需要全局匹配,应该在返回一个包含所有满足要求结果的迭代器的方法中进行,然后对这个被返回的迭代器进行迭代遍历,以实现全局匹配效果。仍然以提取字符串为例,我们需要将上一个例子中的 find 方法改成 find_iter 方法。实现上一个例子的全局匹配提取字符串的代码改版如下:let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
let text = "第一个年月日:2021-01-01, 第二个年月日:2022-02-01, 第三个年月日:2023-03-01";
for date in re.find_iter(text) {
println!("{}", date.as_str());
}其输出结果为:2021-01-01
2022-02-01
2023-03-01实际上,这个返回的迭代器就可以视为类似的数据容器使用,当然你也可以使用一个 vector 去容纳这些匹配的结果。不过总之而言相比于 JavaScript 中的那个例子,确实还是麻烦了不少。4.3 忽略大小写匹配大小写是针对英文字符的,这种匹配模式中,对于字母的大小写形式将不做区分,比如 i 和 I 都将被视作同一个字符。以 JavaScript 为例,只需要在正则表达式末尾使用一个 i,表示忽略大小写:console.log(/^h/i.test("Hello")); // true
console.log(/^h/i.test("hello")); // true在 rust 中,我们目前使用只能 内联匹配模式 语法,给一个忽略指定字符大小写的例子:let re = Regex::new(r"(?i)hello").unwrap();
let text1 = "Hello world!";
let text2 = "hello world!";
println!("{}", re.find(text1).unwrap().as_str());
println!("{}", re.find(text2).unwrap().as_str());输出结果为:Hello
hello可以看到不论 h 字母大小写,都成功匹配提取出了hello。4.4 多行匹配模式在上一节已经接触了 内联匹配模式 的用法。这一节同样使用内联匹配模式给出一个 rust 多行匹配的例子:let re = Regex::new(r"(?m)^\d+").unwrap();
let text = "1. First line\n2. Second line\n3. Third line";
for line in re.find_iter(text) {
println!("{}", line.as_str());
}输出为:1
2
34.5 允许 . 去匹配新的行这一节同样使用内联匹配模式给出一个 rust 中,允许 . 去匹配新的行模式的例子:let re = Regex::new(r"(?s)hello.world").unwrap();
let text = "hello\nworld";
if let Some(matched) = re.find(text) {
println!("已匹配:{}", matched.as_str());
}输出为:已匹配:hello
world4.6 内联匹配的组合你可以使用 i、s、m 的组合,实现同时使用多个匹配模式,如 (?is) 和 (?im),可以参考 内联匹配模式 。4.7 视为 Unicode 码位序列这一节给出一个 Rust 中,将 pattern 视为 Unicode 码位序列的例子:let re = Regex::new(r"\p{Han}+").unwrap();
let text = "你好,世界!";
if let Some(matched) = re.find(text) {
println!("已匹配: {}", matched.as_str());
}输出结果为:已匹配: 你好5. Regex API 解析本节解析 Regex 的 API,内容主要为对 API 文档 https://docs.rs/regex/1.8.3/regex/struct.Regex.html 的中文翻译,对于部分 API,增加了一些实际使用的例子。5.1 核心正则表达式方法5.1.1 new 方法描述pub fn new(re: &str) -> Result<Regex, Error>编译正则表达式。一旦编译,它可以重复使用,以搜索,分割或替换字符串中的文本。如果给出了无效的表达式,则返回一个错误。5.1.2 is_match 方法描述Pub FN is_match(&self, text: &str) -> bool当且仅当给定的字符串中有匹配的正则表达式时,返回true。如果您需要做的只是测试一个匹配,那么建议使用这种方法,因为底层的匹配引擎可以做更少的工作。例子测试某些文本是否包含至少一个正好为13个 Unicode 单词字符的单词:let text = "I categorically deny having triskaidekaphobia.";
assert!(Regex::new(r"\b\w{13}\b").unwrap().is_match(text));5.1.3 find 方法描述pub fn find<'t>(&self, text: &'t str) -> Option<Match<'t>>返回文本中最左边第一个匹配的开始和结束字节范围。如果不存在匹配,则返回 None。请注意,这应该只在您想要发现匹配的位置时使用。如果使用 is_match,测试匹配的存在会更快。例子用13个Unicode单词字符查找第一个单词的开始和结束位置:let text = "I categorically deny having triskaidekaphobia.";
let mat = Regex::new(r"\b\w{13}\b").unwrap().find(text).unwrap();
assert_eq!(mat.start(), 2);
assert_eq!(mat.end(), 15);5.4 find_iter 方法描述pub fn find_iter<'r, 't>(&'r self, text: &'t str) -> Matches<'r, 't>为 text 中每个连续的非重叠匹配返回一个迭代器,返回相对于 text 的起始和结束字节索引。例子查找每个单词的开始和结束位置,每个单词正好有13个Unicode单词字符:let text = "Retroactively relinquishing remunerations is reprehensible.";
for mat in Regex::new(r"\b\w{13}\b").unwrap().find_iter(text) {
println!("{:?}", mat);
}5.1.5 captures 方法描述pub fn captures<'t>(&self, text: &'t str) -> Option<Captures<'t>>返回与文本中最左边的第一个匹配相对应的捕获组。捕获组 0 始终对应于整个匹配。如果找不到匹配,则不返回任何内容。如果您需要访问捕获组匹配的位置,您应该只使用捕获。否则,查找会更快地发现整个匹配的位置。例子假设您有一些带有电影名称及其上映年份的文本,如“‘Citizen Kane’ (1941)”。如果我们可以搜索这样的文本,同时还可以分别提取电影名称和上映年份,那就太好了。let re = Regex::new(r"'([^']+)'\s+\((\d{4})\)").unwrap();
let text = "Not my favorite movie: 'Citizen Kane' (1941).";
let caps = re.captures(text).unwrap();
assert_eq!(caps.get(1).unwrap().as_str(), "Citizen Kane");
assert_eq!(caps.get(2).unwrap().as_str(), "1941");
assert_eq!(caps.get(0).unwrap().as_str(), "'Citizen Kane' (1941)");
// You can also access the groups by index using the Index notation.
// Note that this will panic on an invalid index.
assert_eq!(&caps[1], "Citizen Kane");
assert_eq!(&caps[2], "1941");
assert_eq!(&caps[0], "'Citizen Kane' (1941)");请注意,完全匹配发生在捕获组0。每个后续捕获组按其开始的顺序进行索引 ( 。通过使用命名的捕获组,我们可以使这个示例更加清晰:let re = Regex::new(r"'(?P<title>[^']+)'\s+\((?P<year>\d{4})\)")
.unwrap();
let text = "Not my favorite movie: 'Citizen Kane' (1941).";
let caps = re.captures(text).unwrap();
assert_eq!(caps.name("title").unwrap().as_str(), "Citizen Kane");
assert_eq!(caps.name("year").unwrap().as_str(), "1941");
assert_eq!(caps.get(0).unwrap().as_str(), "'Citizen Kane' (1941)");
// You can also access the groups by name using the Index notation.
// Note that this will panic on an invalid group name.
assert_eq!(&caps["title"], "Citizen Kane");
assert_eq!(&caps["year"], "1941");
assert_eq!(&caps[0], "'Citizen Kane' (1941)");这里我们命名捕获组,我们可以用 name 方法或带 &str 的Index 符号来访问它。请注意,使用 get 或带有 usize 的 Index 符号仍然可以访问命名的捕获组。第 0 个捕获组始终未命名,因此必须始终使用 get(0)或[0]` 来访问它。5.1.6 captures_iter 方法描述pub fn captures_iter<'r, 't>(&'r self, text: &'t str) -> CaptureMatches<'r, 't>返回文本中匹配的所有非重叠捕获组的迭代器。这在操作上与 find_iter 相同,除了它产生关于捕获组匹配的信息。例子我们可以使用它在一些文本中查找所有电影名称及其发行年份,其中电影的格式类似于“’ Title’ (xxxx)”:let re = Regex::new(r"'(?P<title>[^']+)'\s+\((?P<year>\d{4})\)")
.unwrap();
let text = "'Citizen Kane' (1941), 'The Wizard of Oz' (1939), 'M' (1931).";
for caps in re.captures_iter(text) {
println!("Movie: {:?}, Released: {:?}",
&caps["title"], &caps["year"]);
}输出为:Movie: Citizen Kane, Released: 1941
Movie: The Wizard of Oz, Released: 1939
Movie: M, Released: 19315.1.7 split 方法描述pub fn split<'r, 't>(&'r self, text: &'t str) -> Split<'r, 't>返回由匹配的正则表达式分隔的文本子字符串的迭代器。也就是说,迭代器的每个元素对应于正则表达式不匹配的文本。此方法不会复制给定的文本。例子要拆分由任意数量的空格或制表符分隔的字符串:let re = Regex::new(r"[ \t]+").unwrap();
let fields: Vec<&str> = re.split("a b \t c\td e").collect();
assert_eq!(fields, vec!["a", "b", "c", "d", "e"]);5.1.8 splitn 方法描述pub fn splitn<'r, 't>(&'r self, text: &'t str, limit: usize) -> SplitN<'r, 't>返回最多有限个文本子字符串的迭代器,这些子字符串由正则表达式的匹配项分隔。(限制为0将不会返回任何子字符串。)也就是说,迭代器的每个元素对应于正则表达式不匹配的文本。字符串中未被拆分的剩余部分将是迭代器中的最后一个元素。此方法不会复制给定的文本。例子获取某些文本中的前两个单词:let re = Regex::new(r"\W+").unwrap();
let fields: Vec<&str> = re.splitn("Hey! How are you?", 3).collect();
assert_eq!(fields, vec!("Hey", "How", "are you?"));5.1.9 replace 方法描述pub fn replace<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str>用提供的替换项替换最左边的第一个匹配项。替换可以是一个常规字符串(其中�和N和name被扩展以匹配捕获组),也可以是一个获取匹配捕获并返回替换字符串的函数。如果没有找到匹配,则返回该字符串的副本,不做任何更改。替换字符串语法替换文本中 $name 的所有实例都将替换为相应的捕获组 name。name 可以是与捕获组的索引相对应的整数(按左括号的顺序计数,其中0表示整个匹配),也可以是与命名的捕获组相对应的名称(由字母、数字或下划线组成)。如果 name 不是有效的捕获组(无论该名称不存在还是不是有效的索引),那么它将被替换为空字符串。使用尽可能长的名称。例如,$1a 查找名为 1a 的捕获组,而不是索引 1 处的捕获组。要对名称进行更精确的控制,请使用大括号,例如 ${1}a。要编写文字 $请使用$$。例子注意,这个函数对于替换是多态的。在典型的用法中,这可能只是一个普通的字符串:let re = Regex::new("[^01]+").unwrap();
assert_eq!(re.replace("1078910", ""), "1010");但是任何满足替代品特性的东西都可以工作。例如,类型为 |&Captures| -> String 的闭包提供了对与匹配相对应的捕获的直接访问。这允许人们容易地访问捕获组匹配:let re = Regex::new(r"([^,\s]+),\s+(\S+)").unwrap();
let result = re.replace("Springsteen, Bruce", |caps: &Captures| {
format!("{} {}", &caps[2], &caps[1])
});
assert_eq!(result, "Bruce Springsteen");但是一直使用这个有点麻烦。相反,支持一个简单的语法,将 $name 展开到相应的捕获组中。这是最后一个示例,但是使用命名捕获组的扩展技术:let re = Regex::new(r"(?P<last>[^,\s]+),\s+(?P<first>\S+)").unwrap();
let result = re.replace("Springsteen, Bruce", "$first $last");
assert_eq!(result, "Bruce Springsteen");请注意,使用 $2 而不是 $first 或 $1 而不是 $last 会产生相同的结果。要编写文字 $ 请使用 $$。有时,替换字符串需要使用花括号来描述捕获组替换和周围的文字文本。例如,如果我们想用下划线将两个单词连在一起:let re = Regex::new(r"(?P<first>\w+)\s+(?P<second>\w+)").unwrap();
let result = re.replace("deep fried", "${first}_$second");
assert_eq!(result, "deep_fried");如果没有花括号,将使用捕获组名 first_ 因为它不存在,所以将被空字符串替换。最后,有时您只想替换一个文字字符串,而不考虑捕获组扩展。这可以通过用NoExpand包装一个字节字符串来实现:use regex::NoExpand;
let re = Regex::new(r"(?P<last>[^,\s]+),\s+(\S+)").unwrap();
let result = re.replace("Springsteen, Bruce", NoExpand("$2 $last"));
assert_eq!(result, "$2 $last");5.1.10 replace_all 方法描述pub fn replace_all<'t, R: Replacer>(
&self,
text: &'t str,
rep: R
) -> Cow<'t, str>用提供的替换内容替换文本中所有不重叠的匹配内容。这与调用limit设置为0的replacen是一样的。5.1.11 replacen 方法描述pub fn replacen<'t, R: Replacer>(
&self,
text: &'t str,
limit: usize,
rep: R
) -> Cow<'t, str>5.2 高级或“低级”搜索方法5.2.1 shortest_match 方法描述pub fn shortest_match(&self, text: &str) -> Option<usize>返回给定文本中匹配的结束位置。该方法可以具有与 is_match 相同的性能特征,除了它提供了匹配的结束位置。特别是,返回的位置可能比通过 Regex::find 找到的最左边第一个匹配的正确结尾要短。注意,不能保证这个例程找到最短或“最早”的可能匹配。相反,这个 API 的主要思想是,它返回内部正则表达式引擎确定发生匹配的点的偏移量。这可能因使用的内部正则表达式引擎而异,因此偏移量本身可能会改变。例子通常,a+ 会匹配某个文本中 a 的整个第一个序列,但是shortest_match 一看到第一个 a 就会放弃:let text = "aaaaa";
let pos = Regex::new(r"a+").unwrap().shortest_match(text);
assert_eq!(pos, Some(1));5.2.2 shortest_match_at 方法描述pub fn shortest_match_at(&self, text: &str, start: usize) -> Option<usize>返回与 shortest_match 相同的值,但从给定的偏移量开始搜索。起点的意义在于它考虑了周围的环境。例如,\A 定位点只能在 start == 0 时匹配。例子let re = Regex::new(r"\d+").unwrap();
// 在字符串中查找最短匹配
let text = "123456789";
let shortest_match = re.shortest_match_at(text, 0).unwrap();
println!("Shortest match found at index {}", shortest_match.end());5.2.3 is_match_at 方法描述pub fn is_match_at(&self, text: &str, start: usize) -> bool返回与 is_match 相同的值,但从给定的偏移量开始搜索。起点的意义在于它考虑了周围的环境。例如,\A 锚点只能在 start == 0 时匹配。例子let re = Regex::new(r"\d{3}-\d{2}-\d{4}").unwrap();
let s = "123-45-6789";
// 使用is_match_at方法判断字符串是否匹配正则表达式
if re.is_match_at(s, 0) {
println!("Matched!");
} else {
println!("Not matched!");
}5.2.4 find_at 方法描述pub fn find_at<'t>(&self, text: &'t str, start: usize) -> Option<Match<'t>>返回与 find 相同的值,但从给定的偏移量开始搜索。起点的意义在于它考虑了周围的环境。例如,\A 锚点只能在 start == 0 时匹配。例子let s = "hello world";
let re = Regex::new(r"world").unwrap();
// 使用find_at方法查找字符串中第一个匹配正则表达式的位置
let pos = re.find_at(s, 0).unwrap().start();
// 输出匹配位置
println!("Match found at position: {}", pos);5.2.5 captures_at 方法描述ub fn captures_at<'t>(
&self,
text: &'t str,
start: usize
) -> Option<Captures<'t>>返回与 Regex::captures 相同的值,但从给定的偏移量开始搜索。起点的意义在于它考虑了周围的环境。例如,\A 锚点只能在 start == 0 时匹配。例子let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
let text = "2022-10-31";
let year = re.captures_at(text, 1).unwrap().as_str();
let month = re.captures_at(text, 2).unwrap().as_str();
let day = re.captures_at(text, 3).unwrap().as_str();
println!("Year: {}, Month: {}, Day: {}", year, month, day);5.2.6 captures_read 方法描述pub fn captures_read<'t>(
&self,
locs: &mut CaptureLocations,
text: &'t str
) -> Option<Match<'t>>这类似于 captures ,但使用 CaptureLocations 而不是 Captures 来分摊分配。若要创建 CaptureLocations 值,请使用Regex::capture_locations 方法。如果匹配成功,将返回总匹配,总匹配等同于第 0 个捕获组。例子// 用于匹配字符串中的数字
let re = Regex::new(r"\d+").unwrap();
let test_str = "abc123def456ghi789jkl";
// 使用captures_read方法,从字符串中读取匹配到的数字
let mut captures = re.captures_read(test_str.as_bytes());
// 遍历匹配到的数字
while let Some(capture) = captures.next() {
println!("{}", std::str::from_utf8(capture.unwrap().as_bytes()).unwrap());
}5.2.7 captures_read_at 方法描述fn captures_read_at<'t>(
&self,
locs: &mut CaptureLocations,
text: &'t str,
start: usize
) -> Option<Match<'t>>返回与捕获相同的值,但从给定的偏移量开始搜索,并填充给定的捕获位置。起点的意义在于它考虑了周围的环境。例如,\A锚点只能在start == 0时匹配。例子let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
let text = "Today is 2022-01-01.";
if let Some(captures) = re.captures(text) {
println!("Year: {}, Month: {}, Day: {}", captures.get(1).unwrap().as_str(), captures.get(2).unwrap().as_str(), captures.get(3).unwrap().as_str());
} else {
println!("No match found");
}5.3 辅助方法5.3.1 as_str 方法描述pub fn as_str(&self) -> &str返回该正则表达式的原始字符串。例子let re = Regex::new(r"\d+").unwrap();
let text = "2021-08-01";
let result = re.find(text).unwrap();
println!("{}", result.as_str());5.3.2 capture_names 方法描述pub fn capture_names(&self) -> CaptureNames<'_>返回捕获名称的迭代器。5.3.3 captures_len 方法描述pub fn captures_len(&self) -> usize返回捕获的数量。5.3.4 static_captures_len 方法描述pub fn static_captures_len(&self) -> Option<usize>返回出现在每个可能匹配项中的捕获组的总数。如果捕获组的数量因匹配而异,则返回 None。也就是说,只有当匹配组的数量不变或“static”时,才会返回值。注意,像 Regex::captures_len 一样,这也包含了对应于整个匹配的隐式捕获组。因此,当返回一个非 None 值时,它保证至少为 1。换句话说,返回值 Some(0) 是不可能的。例子这个例子显示了静态数量的捕获组可用和不可用的几种情况。use regex::Regex;
let len = |pattern| {
Regex::new(pattern).map(|re| re.static_captures_len())
};
assert_eq!(Some(1), len("a")?);
assert_eq!(Some(2), len("(a)")?);
assert_eq!(Some(2), len("(a)|(b)")?);
assert_eq!(Some(3), len("(a)(b)|(c)(d)")?);
assert_eq!(None, len("(a)|b")?);
assert_eq!(None, len("a|(b)")?);
assert_eq!(None, len("(b)*")?);
assert_eq!(Some(2), len("(b)+")?);5.3.5 static_captures_len 方法描述pub fn capture_locations(&self) -> CaptureLocations返回一组空的捕获位置,这些位置可以在多次调用 captures_read 或 captures_read_at 时重用。附录: 查询元字符表字符描述\将下一个字符标记为一个特殊字符、或一个原义字符、或一个 向后引用、或一个八进制转义符。例如,‘n’ 匹配字符 “n”。‘\n’ 匹配一个换行符。序列 ‘\’ 匹配 “” 而 “(” 则匹配 “(”。^匹配输入字符串的开始位置。如果设置了 RegExp 对象的 Multiline 属性,^ 也匹配 ‘\n’ 或 ‘\r’ 之后的位置。$匹配输入字符串的结束位置。如果设置了RegExp 对象的 Multiline 属性,$ 也匹配 ‘\n’ 或 ‘\r’ 之前的位置。*匹配前面的子表达式零次或多次。例如,zo* 能匹配 “z” 以及 “zoo”。* 等价于{0,}。+匹配前面的子表达式一次或多次。例如,‘zo+’ 能匹配 “zo” 以及 “zoo”,但不能匹配 “z”。+ 等价于 {1,}。?匹配前面的子表达式零次或一次。例如,“do(es)?” 可以匹配 “do” 或 “does” 。? 等价于 {0,1}。{n}n 是一个非负整数。匹配确定的 n 次。例如,‘o{2}’ 不能匹配 “Bob” 中的 ‘o’,但是能匹配 “food” 中的两个 o。{n,}n 是一个非负整数。至少匹配n 次。例如,‘o{2,}’ 不能匹配 “Bob” 中的 ‘o’,但能匹配 “foooood” 中的所有 o。‘o{1,}’ 等价于 ‘o+’。‘o{0,}’ 则等价于 ‘o*’。{n,m}m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,“o{1,3}” 将匹配 “fooooood” 中的前三个 o。‘o{0,1}’ 等价于 ‘o?’。请注意在逗号和两个数之间不能有空格。?当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 “oooo”,‘o+?’ 将匹配单个 “o”,而 ‘o+’ 将匹配所有 ‘o’。.匹配除换行符(\n、\r)之外的任何单个字符。要匹配包括 ‘\n’ 在内的任何字符,请使用像"(.|\n)"的模式。(pattern)匹配 pattern 并获取这一匹配。所获取的匹配可以从产生的 Matches 集合得到,在VBScript 中使用 SubMatches 集合,在JScript 中则使用 $0…$9 属性。要匹配圆括号字符,请使用 ‘(’ 或 ‘)’。(?:pattern)匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 “或” 字符 (|) 来组合一个模式的各个部分是很有用。例如, 'industr(?:y|ies) 就是一个比 ‘industry|industries’ 更简略的表达式。(?=pattern)正向肯定预查(look ahead positive assert),在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)“能匹配"Windows2000"中的"Windows”,但不能匹配"Windows3.1"中的"Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。(?!pattern)正向否定预查(negative assert),在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如"Windows(?!95|98|NT|2000)“能匹配"Windows3.1"中的"Windows”,但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。(?<=pattern)反向(look behind)肯定预查,与正向肯定预查类似,只是方向相反。例如,"`(?<=95(?<!pattern)反向否定预查,与正向否定预查类似,只是方向相反。例如"`(?<!95x|y匹配 x 或 y。例如,‘z|food’ 能匹配 “z” 或 “food”。‘(z|f)ood’ 则匹配 “zood” 或 “food”。[xyz]字符集合。匹配所包含的任意一个字符。例如, ‘[abc]’ 可以匹配 “plain” 中的 ‘a’。[^xyz]负值字符集合。匹配未包含的任意字符。例如, ‘[^abc]’ 可以匹配 “plain” 中的’p’、‘l’、‘i’、‘n’。[a-z]字符范围。匹配指定范围内的任意字符。例如,‘[a-z]’ 可以匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符。[^a-z]负值字符范围。匹配任何不在指定范围内的任意字符。例如,‘[^a-z]’ 可以匹配任何不在 ‘a’ 到 ‘z’ 范围内的任意字符。\b匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。\B匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。\cx匹配由 x 指明的控制字符。例如, \cM 匹配一个 Control-M 或回车符。x 的值必须为 A-Z 或 a-z 之一。否则,将 c 视为一个原义的 ‘c’ 字符。\d匹配一个数字字符。等价于 [0-9]。\D匹配一个非数字字符。等价于 [^0-9]。\f匹配一个换页符。等价于 \x0c 和 \cL。\n匹配一个换行符。等价于 \x0a 和 \cJ。\r匹配一个回车符。等价于 \x0d 和 \cM。\s匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。\S匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。\t匹配一个制表符。等价于 \x09 和 \cI。\v匹配一个垂直制表符。等价于 \x0b 和 \cK。\w匹配字母、数字、下划线。等价于’[A-Za-z0-9_]'。\W匹配非字母、数字、下划线。等价于 ‘[^A-Za-z0-9_]’。\xn匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,‘\x41’ 匹配 “A”。‘\x041’ 则等价于 ‘\x04’ & “1”。正则表达式中可以使用 ASCII 编码。\num匹配 num,其中 num 是一个正整数。对所获取的匹配的引用。例如,‘(.)\1’ 匹配两个连续的相同字符。\n标识一个八进制转义值或一个向后引用。如果 \n 之前至少 n 个获取的子表达式,则 n 为向后引用。否则,如果 n 为八进制数字 (0-7),则 n 为一个八进制转义值。\nm标识一个八进制转义值或一个向后引用。如果 \nm 之前至少有 nm 个获得子表达式,则 nm 为向后引用。如果 \nm 之前至少有 n 个获取,则 n 为一个后跟文字 m 的向后引用。如果前面的条件都不满足,若 n 和 m 均为八进制数字 (0-7),则 \nm 将匹配八进制转义值 nm。\nml如果 n 为八进制数字 (0-3),且 m 和 l 均为八进制数字 (0-7),则匹配八进制转义值 nml。\un匹配 n,其中 n 是一个用四个十六进制数字表示的 Unicode 字符。例如, \u00A9 匹配版权符号 (?)。优先级(从上到下)运算符描述\转义符(), (?:), (?=), [] 圆括号和方括号*, +, ?, {n}, {n,}, {n,m}限定符^, $, \任何元字符、任何字符定位点和序列(即:位置和顺序)``内联匹配模式匹配模式语法描述(?i)右侧的表达式忽略大小写(?s)右侧的表达式单行匹配(?m)右侧的表示式开启指定多行(?is)右侧的表示式忽略大小写 且 单行匹配(?im)右侧的表示式忽略大小写 且 多行匹配
WebAssembly入门:构建高性能的浏览器应用
什么是WebAssembly?
WebAssembly是一种低级别的、可移植的二进制格式,用于在现代浏览器中运行高性能的应用程序。它是由W3C(World Wide Web Consortium)和多家技术公司共同推动的开放标准。与传统的JavaScript相比,WebAssembly具有更高的执行速度,能够在浏览器中实现近乎原生的性能。
WebAssembly的优势
高性能: WebAssembly的二进制格式允许更快的加载和执行速度,使得应用程序能够在浏览器中以接近原生的速度运行。多语言支持: WebAssembly支持多种编程语言,如C/C++、Rust、Go等,开发者可以使用自己喜欢的语言编写应用程序,并将其编译为WebAssembly模块。安全性: WebAssembly运行在浏览器的沙箱环境中,提供了额外的安全保障,防止恶意代码对用户设备和数据的攻击。
使用WebAssembly构建浏览器应用的步骤
步骤一:选择编程语言
选择一种适合你的需求和技能的编程语言。目前,许多编程语言都有对WebAssembly的支持,比如C/C++、Rust、Go等。选择一种你熟悉或感兴趣的语言,以便更好地使用WebAssembly构建应用程序。
步骤二:编写代码并编译为WebAssembly模块
使用选定的编程语言编写应用程序代码,并将其编译为WebAssembly模块。每种语言都有相应的工具链和编译器,可以将代码转换为WebAssembly格式。
步骤三:加载和执行WebAssembly模块
在浏览器中,使用JavaScript加载和执行WebAssembly模块。可以使用浏览器提供的WebAssembly API或第三方库来处理加载和运行过程。
步骤四:与JavaScript交互
WebAssembly与JavaScript可以互相调用函数和共享内存,这使得在WebAssembly和JavaScript之间进行无缝交互变得容易。可以通过定义导入和导出函数来实现函数的调用。
示例:使用C++编写WebAssembly模块
以下是使用C++编写并编译为WebAssembly模块的示例代码:
// main.cpp
#include <iostream>
extern "C" {
void greet() {
std::cout << "Hello, WebAssembly!" << std::endl;
}
}
编译为WebAssembly模块的命令:
$ emcc main.cpp -o hello.wasm
在JavaScript中加载和执行WebAssembly模块的代码:
// index.js
fetch('hello.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes))
.then(results => {
const instance = results.instance;
instance.exports.greet();
});
结论
WebAssembly是一项强大的技术,它为开发者提供了构建高性能、跨平台的浏览器应用的能力。通过选择合适的编程语言,并遵循简单的构建步骤,你可以开始利用WebAssembly的优势来开发出更快、更强大的Web应用。
希望这篇文章对你入门WebAssembly有所帮助!如果你对WebAssembly感兴趣,欢迎继续深入学习和探索。
如果你有任何问题或想分享你的经验,请在下方评论区留言。谢谢!
希望这篇文章符合您的要求!如果您有任何其他需求或疑问,请随时告诉我。