windows服务器部署RustDesk服务器
Rust 初体验
安装
打开官网,下载 rustup-init.exe, 选择缺省模式(1)安装。
国内源设置
在 .Cargo 目录下新建 config 文件,添加如下内容:
[source.crates-io] registry = "https:///rust-lang/crates.io-index" # 指定镜像 replace-with = 'tuna' # 清华大学 [source.tuna] registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git" [registries.rsproxy] index = "https://rsproxy.cn/crates.io-index" [net] git-fetch-with-cli = true第一个程序
使用任意代码编辑器,编写如下代码,保存为后缀是.rs的源文件,如 。
fn main() { println!("Hello World!"); }了解C语言的同学,会发现这个程序和C语言的hello.c非常相似。不同之处在于,fn 表示函数,main 表示程序的入口,println! 表示输出。! 表示宏,即 println 是个宏,而不是函数。
在源文件文件夹里,运行 rustc 进行编译,生成 hello.exe。
运行 hello.exe,输出:
Hello World!
使用 Cargo 管理
cargo new project_name 创建一个新项目
cd project_name 进入项目
cargo build 编译生成Debug版本
cargo build --release 编译并生成可执行文件
cargo run 运行
cargo check 检查错误(不编译)
cargo 常用命令
build, b Compile the current package check, c Analyze the current package and report errors, but don't build object files clean Remove the target directory doc, d Build this package's and its dependencies' documentation new Create a new cargo package init Create a new cargo package in an existing directory add Add dependencies to a manifest file remove Remove dependencies from a manifest file run, r Run a binary or example of the local package test, t Run the tests bench Run the benchmarks update Update dependencies listed in Cargo.lock search Search registry for crates publish Package and upload this package to the registry install Install a Rust binary. Default location is $HOME/.cargo/bin uninstall Uninstall a Rust binary变量
Rust 是强类型语言,使用关键字 let 声明变量后,具有自动判断变量类型的能力,如:
let a = 123; 则默认 a 为整型数字,且精度不允许变化,即 a 的值不可改变,可以理解为“只读”。
这与 C 语言中的 const 修饰符起到的效果一样。在 C 语言中 int const a = 10; a 的值也不允许改变。
Rust 中如果要让变量可变,需要用 mut 关键词:
let mut a = 123; a = 456;指定数据类型
let a: u64 = 123; // 后面跟数据类型u64,表示无符号64位整型变量。 let y: f32 = 3.0; // 后面跟数据类型f32,表示32位浮点型变量。Shadowing(影射)
变量名可以被重新使用,如:
let x = 5; let x = x + 1; let x = x * 2;最终 x 的值为12。从语法上说, 变量 x 可以作为右值。
注释
可以使用 C/C++, Java 注释。
另外,可用 /// 表示文档开头注释
函数
定义函数,如需要参数,必须声明参数名称、类型。
fn another_function(x: i32, y: i32) { }可在{}包括的块里,编写较为复杂的表达式,即所谓的函数体表达式。注:函数体表达式,并不能等同于函数体,不能使用 return 关键字。
let y = { let x = 3; x + 1 };返回值类型声明
在参数声明之后用->来声明函数返回值的类型。 注:Rust 不支持自动返回值类型判断。
fn add(a: i32, b: i32) -> i32 { return a + b; }条件语句
if a > 0 { b = 1; } else if a < 0 { b = -1; } else { b = 0; } println!("b is {}", b);if 后面不需要小括号,{} 必用。右括号 } 后不加 ;。
条件表达式必须是 bool 类型,不同于 C/C++ 的非 0 即真。
可以使用 if-else 结构实现类似于三元条件运算表达式 (A ? B : C) 。如:
let number = if a > 0 { 1 } else { -1 };注:此处编译报错,检测出来其中 0 为 i32,代码未指定数据类型。故将0改为&0:
let number = if a > &0 { 1 } else { -1 };for 循环
最常用的循环结构。
let a = [10, 20, 30, 40, 50]; for i in a.iter() { println!("值为 : {}", i); }a.iter() 代表 a 的迭代器(iterator)
也可以通过下标来访问数组。如:
let a = [10, 20, 30, 40, 50]; for i in 0..5 { println!("a[{}] = {}", i, a[i]); }以上代码会报错。正确写法是:
const a: [i32; 5] = [10, 20, 30, 40, 50]; for i in 0..5 { println!("a[{}] = {}", i, a[i]); }可见,代码检查更严格。
while 循环
let mut number = 1; // mut 表示number 在循环体内可以改变。 while number != 4 { println!("{}", number); number += 1; }无限循环结构 loop
可以通过 break 关键字,起到类似 return 一样的作用,使整个循环退出并给予外部一个返回值。
let mut i = 0; let location = loop { let ch = s[i]; if ch == '*' { break i; } i += 1; };以上代码会报错,可以改为:
fn main() { let s = "hello world"; let mut i = 0; let location = loop { let ch = s.chars().nth(i).unwrap(); if ch == ' ' { break i; } i += 1; }; println!("{}", location); }变量与数据交互
方式有移动(Move)和克隆(Clone)两种,前者不保存移动前的变量,后者继续保留移动前的变量。
let s1 = String::from("hello"); let s2 = s1; println!("{}, world!", s1); // 错误!s1 已经失效 let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2); //ok引用(Reference)
& 运算符可以取变量的"引用"。当一个变量的值被引用时,变量本身不会被认定无效。注:C++中的概念。
引用不会获得值的所有权。引用只能租借(Borrow)值的所有权。
引用本身也是一个类型并具有一个值,这个值记录的是别的值所在的位置,但引用不具有所指值的所有权。
&mut 修饰可变的引用类型。
可变引用不允许多重引用,但不可变引用可以。
以上机制设计,主要出于对并发状态下发生数据访问碰撞的考虑,使得编译阶段就避免发生。
垂悬引用(Dangling References)
Rust 语言里不允许出现,如果有,编译器会发现它。
字符串切片(String Slice)
let s = String::from("broadcast"); let part1 = &s[0..5]; let part2 = &s[5..9]; println!("{}={}+{}", s, part1, part2);在 Rust 中有两种常用的字符串类型:str 和 String。
str 是 Rust 核心语言类型,凡用双引号包括的字符串常量整体的类型性质都是 &str。
String 类型是 Rust 标准公共库提供的一种数据类型,其功能更完善。String 和 str 都支持切片,切片的结果是 &str 类型的数据。
注:切片结果必须是引用类型,但开发者必须明示:
let slice = &s[0..3];除了字符串,其他一些线性数据结构也支持切片操作,如:
let arr = [1, 3, 5, 7, 9]; let part = &arr[0..3]; for i in part.iter() { println!("{}", i); }结构体
定义结构体:
struct Site { domain: String, name: String, nation: String, found: u32 }注:Rust 里 struct 语句仅用来定义,不能声明实例,结尾不需要; 符号,且每个字段定义之后用 , 分隔。
Rust 很多地方受 JavaScript 影响,在实例化结构体的时候用 JSON 对象的 key: value 语法来实现定义:
let runoob = Site { domain: String::from("www.runoob.com"), name: String::from("RUNOOB"), nation: String::from("China"), found: 2013 };如果正在实例化的结构体,有字段名称与现存变量名称一样,可以简化书写。
let domain = String::from("www.runoob.com"); let name = String::from("RUNOOB"); let runoob = Site { domain, // 等同于 domain : domain, name, // 等同于 name : name, nation: String::from("China"), traffic: 2013 };新建一个结构体实例,如果其中大部分属性需要被设置成与现存的一个结构体属性一样,仅需更改其中一两个字段的值,可以使用结构体更新语法:
let site = Site { domain: String::from("www.runoob.com"), name: String::from("RUNOOB"), ..runoob };注:…runoob 后面不可以有逗号。这种语法不允许一成不变的复制另一个结构体实例,至少重新设定一个字段的值才能引用其他实例的值。
元组结构体
struct Color(u8, u8, u8); struct Point(f64, f64); let black = Color(0, 0, 0); let origin = Point(0.0, 0.0);元组结构体对象的使用方式和元组一样,通过 . 和下标来进行访问:
struct Color(u8, u8, u8); struct Point(f64, f64); let black = Color(0, 0, 0); let origin = Point(0.0, 0.0); println!("black = ({}, {}, {})", black.0, black.1, lack.2); println!("origin = ({}, {})", origin.0, origin.1);结构体必须掌握字段值所有权,因为结构体失效的时候会释放所有字段。
结构体输出
导入调试库 #[derive(Debug)]
在 println 和 print 宏中可以用 {:?} 占位符输出一整个结构体;结构体如果属性较多,可使用另一个占位符 {:#?} 。
结构体方法
结构体方法的第一个参数必须是 &self,不需声明类型。
struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } } fn main() { let rect1 = Rectangle { width: 30, height: 50 }; println!("rect1's area is {}", rect1.area()); }这里与Python中的Class方法有神似。
结构体关联函数
不依赖实例,使用时需要声明是在哪个 impl 块中的。String::from函数就是一个"关联函数"。
# [derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn create(width: u32, height: u32) -> Rectangle { Rectangle { width, height } } } fn main() { let rect = Rectangle::create(30, 50); println!("{:?}", rect); }这里的Rectangle中的create函数,即是结构体关联函数。这个概念与 C++语言里面的类成员函数有些相似。不同之处在于, C++ 类成员函数是在类的内部定义,并且使用 this 指针来访问类的实例。
单元结构体
结构体可以只作为一种象征而无需任何成员:
struct UnitStruct;枚举类
# [derive(Debug)] enum Book { Papery, Electronic } fn main() { let book = Book::Papery; println!("{:?}", book); }为枚举类成员添加元组属性描述
enum Book { Papery(u32), Electronic(String), } let book = Book::Papery(1001); let ebook = Book::Electronic(String::from("url://..."));为属性命名,可以用结构体语法:
enum Book { Papery { index: u32 }, Electronic { url: String }, } let book = Book::Papery{index: 1001};match 语法
枚举的目的是对某一类事物的分类,分类的目的是为了对不同的情况进行描述。Rust 通过 match 语句来实现分支结构。
fn main() { enum Book { Papery {index: u32}, Electronic {url: String}, } let book = Book::Papery{index: 1001}; let ebook = Book::Electronic{url: String::from("url...")}; match book { Book::Papery { index } => { println!("Papery book {}", index); }, Book::Electronic { url } => { println!("E-book {}", url); } } }注意其中的,号与;号。
match 枚举类实例 { 分类1 => 返回值表达式, 分类2 => 返回值表达式, ... }对非枚举类进行分支选择时必须注意处理例外情况,即使在例外情况下没有任何要做的事 . 例外情况用下划线 _ 表示:
fn main() { let t = "abc"; match t { "abc" => println!("Yes"), _ => {}, } }Option 枚举类
Rust 标准库中的枚举类,用于填补 Rust 不支持 null 引用的空白。Rust 在语言层面彻底不允许空值 null 的存在,但null 可以高效地解决少量的问题,所以 Rust 引入了 Option 枚举类:
enum Option<T> { Some(T), None, }如果想定义一个可以为空值的类,可以这样:
let opt = Option::Some("Hello");如果想针对 opt 执行某些操作,必须先判断它是否是 Option::None:
fn main() { let opt = Option::Some("Hello"); match opt { Option::Some(something) => { println!("{}", something); }, Option::None => { println!("opt is nothing"); } } }初始值为空的 Option 必须明确类型:
fn main() { let opt: Option<&str> = Option::None; match opt { Option::Some(something) => { println!("{}", something); }, Option::None => { println!("opt is nothing"); } } }这种设计会让空值编程变得不容易,但这正是构建一个稳定高效的系统所需要的。由于 Option 是 Rust 编译器默认引入的,在使用时可以省略 Option:: 直接写 None 或者 Some()。
fn main() { let t = Some(64); match t { Some(64) => println!("Yes"), _ => println!("No"), } }又:
let i = 0; match i { 0 => println!("zero"), _ => {}, }用 if let 语法缩短这段代码:
let i = 0; if let 0 = i { println!("zero"); }如:
fn main() { enum Book { Papery(u32), Electronic(String) } let book = Book::Electronic(String::from("url")); if let Book::Papery(index) = book { println!("Papery {}", index); } else { println!("Not papery book"); } }注意其中 if 语句后的 = 号。
组织管理
Rust 中有三个重要的组织概念:箱、包、模块。
箱(Crate)
"箱"是二进制程序文件或者库文件,存在于"包"中。树状结构,树根是编译器开始运行时编译的源文件所编译的程序。
注意:“二进制程序文件"不一定是"二进制可执行文件”,只能确定是是包含目标机器语言的文件,文件格式随编译环境的不同而不同。
包(Package)
当使用 Cargo 执行 new 命令创建 Rust 工程时,工程目录下会建立一个 Cargo.toml 文件。工程的实质就是一个包,包必须由一个 Cargo.toml 文件来管理,该文件描述了包的基本信息以及依赖项。
一个包最多包含一个库"箱",可以包含任意数量的二进制"箱",但是至少包含一个"箱"(不管是库还是二进制"箱")。
当使用 cargo new 命令创建完包之后,src 目录下会生成一个 main.rs 源文件,Cargo 默认这个文件为二进制箱的根,编译之后的二进制箱将与包名相同。
模块(Module)
对于一个软件工程来说,往往按照所使用的编程语言的组织规范来进行组织,组织模块的主要结构往往是树。Java 组织功能模块的主要单位是类,而 JavaScript 组织模块的主要方式是 function。Rust 中的组织单位是模块(Module)。
这些先进的语言的组织单位可以层层包含,就像文件系统的目录结构一样。Rust 中的组织单位是模块(Module)。
路径分为绝对路径和相对路径。绝对路径从 crate 关键字开始描述。相对路径从 self 或 super 关键字或一个标识符开始描述。Rust 中的路径分隔符是 ::。如:
crate::nation::government::govern();
是描述 govern 函数的绝对路径,相对路径可以表示为:
nation::government::govern();
访问权限
Rust 中有两种简单的访问权:公共(public)和私有(private)。
默认情况下,如果不加修饰符,模块中的成员访问权将是私有的。
如果想使用公共权限,需要使用 pub 关键字。
对于私有的模块,只有在与其平级的位置或下级的位置才能访问,不能从其外部访问。
mod nation { pub mod government { pub fn govern() {} } mod congress { pub fn legislate() {} } mod court { fn judicial() { super::congress::legislate(); } } } fn main() { nation::government::govern(); }这段程序是能通过编译的。请注意观察 court 模块中 super 的访问方法。
use 关键字能够将模块标识符引入当前作用域
mod nation { pub mod government { pub fn govern() {} } } use crate::nation::government::govern; // 解决局部模块路径过长的问题。 fn main() { govern(); }重命名
mod nation { pub mod government { pub fn govern() {} } pub fn govern() {} } use crate::nation::government::govern; use crate::nation::govern as nation_govern; // 解决重名问题 fn main() { nation_govern(); govern(); }还可以与 pub 关键字配合使用:
mod nation { pub mod government { pub fn govern() {} } pub use government::govern; } fn main() { nation::govern(); }Rust 官方标准库字典
所有系统库模块都是被默认导入的,所以在使用的时候只需要使用 use 关键字简化路径即可使用。
在 Rust 中没有 Exception。对可恢复错误用 Result<T, E> 类来处理,对不可恢复错误使用 panic! 宏来处理。
不可恢复错误
fn main() { panic!("error occured"); // 程序将在此处中断,不执行后续语句 println!("Hello, Rust"); }回溯是不可恢复错误的另一种处理方式,它会展开运行的栈并输出所有的信息,然后程序依然会退出。上面的省略号省略了大量的输出信息,我们可以找到我们编写的 panic! 宏触发的错误。
可恢复的错误
Rust 通过 Result<T, E> 枚举类作返回值来进行异常表达。
use std::fs::File; fn main() { let f = File::open("hello.txt"); match f { Ok(file) => { println!("File opened successfully."); }, Err(err) => { println!("Failed to open the file."); } } }可用 if let 语法对简化 match 语法块:
use std::fs::File; fn main() { let f = File::open("hello.txt"); if let Ok(file) = f { println!("File opened successfully."); } else { println!("Failed to open the file."); } }Rust 中可以在 Result 对象后添加 ? 操作符将同类的 Err 直接传递出去.
fn f(i: i32) -> Result<i32, bool> { if i >= 0 { Ok(i) } else { Err(false) } } fn g(i: i32) -> Result<i32, bool> { let t = f(i)?; Ok(t) // 因为确定 t 不是 Err, t 在这里已经是 i32 类型 } fn main() { let r = g(10000); if let Ok(v) = r { println!("Ok: g(10000) = {}", v); } else { println!("Err"); } }? 符的实际作用是将 Result 类非异常的值直接取出,如果有异常就将异常 Result 返回出去。所以,? 符仅用于返回值类型为 Result<T, E> 的函数,其中 E 类型必须和 ? 所处理的 Result 的 E 类型一致。
判断 Result 的 Err 类型,用函数 kind()。
use std::io; use std::io::Read; use std::fs::File; fn read_text_from_file(path: &str) -> Result<String, io::Error> { let mut f = File::open(path)?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) } fn main() { let str_file = read_text_from_file("hello.txt"); match str_file { Ok(s) => println!("{}", s), Err(e) => { match e.kind() { io::ErrorKind::NotFound => { println!("No such file"); }, _ => { println!("Cannot read the file"); } } } } }