首页 » Web前端 » php鸭子类型技巧_从高级轨范员的角度来看Rust 根本常识

php鸭子类型技巧_从高级轨范员的角度来看Rust 根本常识

访客 2024-12-18 0

扫一扫用手机浏览

文章目录 [+]

后来,我又学习了JS,它很像C措辞,而且随处可见。
期间,我也做过一些Java和C#的项目,但后来还是回到了JS。

我也考试测验过学习C(和 C++),虽然得到了Sololearn的证书,但是我从未真正利用过这两种措辞,它们看上去彷佛很繁芜:快速访问内存的功能很酷,但为什么我必须利用free?为什么它不知道超出浸染域时,该当自动开释内存呢?

php鸭子类型技巧_从高级轨范员的角度来看Rust 根本常识

以是,我还是比较喜好利用JS编程,由于我无需考虑内存的问题。
而且,如今与IO干系的操作也不会限定V8的速率。

php鸭子类型技巧_从高级轨范员的角度来看Rust 根本常识
(图片来自网络侵删)

后来,我听说了Rust,这门措辞由Mozilla开拓,多年来一贯雄踞StackOverflow最受喜好编程措辞的榜首,乃至超过了我十分喜好的Typescript(我之以是喜好Typescript,紧张是由于类型安全)。
以是,我就想着该当找机会试一试。

学习资源

我碰着的一大难题是,探求方便理解且简短的好资源。
我不喜好 youtube 视频,我更喜好快速浏览一些文档,或者在通勤路上阅读一些学习资源,而且无需耗费大量流量。

以下是我找到的资源列表:

● 《The Rust Programming Language》(https://doc.rust-lang.org/book/):这是一本在线书本,个中先容了可以利用Rust实现的最常见的功能。

● 《A Gentle Introduction To Rust》(https://stevedonovan.github.io/rust-gentle-intro/):一本简短的书,可以在一两个小时内读完,然后再拿出一两天的韶光考试测验一下示例。
文中涉及的内容比较深入,但很随意马虎节制。

● https://www.reddit.com/r/rust/:这是一个reddit 社区(如果你碰着比较繁芜的问题,则可以发布在此处,等待其他人解答。

● discord社区:你可以通过这个社区向其他开拓职员请教有关Rust的问题。

● Rust By Example(https://doc.rust-lang.org/rust-by-example/index.html):个中先容了一些示例,可以作为入门首选书。

入门

参照Rust网站(https://www.rust-lang.org/)上的解释,利用rustup即可。

如果想创建一个新项目,请运行 cargo init <dir>(如果位于一个空目录内,则不须要指定 <dir>)。

然后即可从src/main.rs开始编写。

与 C 类似,主程序都包装在 main 中。
不同之处在于,它不接管任何参数,也不应该返回一个整数,这些功能该当利用命名空间std::env。

其余,我推举利用CLion并安装Rust扩展。
VSCode 也有 Rust 扩展,但比较之下它的效果很差。
当然你可以利用其他的JetBrains编辑器,但 CLion 具有其他编辑器没有的一些原生功能(比如调试)。
拥有 GitHub 教诲包的学生可以免费利用该插件。

有趣的把稳事变

统统都有浸染域

不仅是变量,就连函数和trait内部也可以利用嵌套函数和use。
这些无法从外部访问,而且如果不该用就不会涌如今代码中。
至少我是这样认为的。

必须遵守的命名方案

变量和函数/方法只能利用小写字母、数字和下划线,比如snake_case,但数字不能放在开头。

构造(和其他类型)、列举(包括列举值)和trait(但不包括它们的函数/方法)须要以大写字母开头,并且不能包含任何下划线。

没有增量运算符

实际上有,你可以利用i += 1。
与赋值相同,该表达式将返回赋值后的值(即,将 i 设置为 i + 1,然后返回 i)。

没有 i++(或者 ++i、i-- 和 --i),由于这些运算符有点混乱。

你确定如下操作的结果吗(尤其是在没有指定措辞的情形下)?

a[i++ + ++i] = i++ + ++i + a[++i]

问题在于,直到最近上述运算的实际行为还是未定义的,这意味着不同的编译器(乃至可能是同一个编译器的不同版本)可能会产生不同的行为。
为理解决这个问题并提高代码的可读性(Rust非常重视可读性和冗长,乃至不惜多敲几次键盘),Rust仅支持 i += 1,险些所有人都知道该表达式的意思是变量i加1,并返回终极结果。
以是,你不必知道 i++ 实际上返回的是原始值(不是新值),而且还会加1。

此外,运算符重载会利用trait,但本文不打算详细谈论。

险些所有的东西都是表达式

除了函数调用之外,还有 if、while、match 和 for 都是表达式。

你可以直策应用 if 来代替其他措辞中常见的三元运算符:

let var = if something { 1 } else { 2 };

循环会根据break的调用返回结果。
你可以利用它,反复重试某个操作,直到成功。

变量

变量通过 let 声明,并且有浸染域。
类型是可选的,Rust 非常善于推断类型(比 Typescript 更出色)。

let var: usize = 1;

上述变量定义了一个类型为usize的变量var(usize是一个32或64位的数字,详细取决于打算机架构)。

你可以重复声明变量。
当重复声明某个变量时,之前声明的变量就会被删除(除非该变量被引用,在这种情形下只有引用会保留,而原始变量会被删除),而且变量的类型也会改变。

let var = 1;let var = \"大众something\公众;

在默认情形下,变量是不可变的。
如果你想修正它们,则须要在 let 之后加上关键字 mut。

let var = 1;var = 2; // 缺点!
不可以修正不可变的变量

let mut var = 1;var = 2;

函数

fn main(arg: u8) -> u8 { // something arg}

函数的行为险些与JS千篇一律,只不过它们并不是数据类型,而且语法上略有不同。

参数的指定与 Typescript 类似,即key: type。
返回类型通过 -> 指定。

有趣的是,虽然 Rust 须要分号,但如果末了一个表达式后面的分号忘写了,它会被作为返回值(纵然没有 return 关键字)。

If语句

if something {} else {} else if something_else {}

if 语句的利用非常基本,不在此赘述。

有一点须要把稳,如非必要,利用括号实际上是缺点的。
你可以利用括号指定实行顺序:

if (something || something_else) && something_other {}

如前所述,if 也可以返回一个值,而该值可用于赋值、参数、返回或其他地方。

let var = if something { 1 } else { 2 };

这里的花括号是必需的。

类型

Rust的类型有两种:基本数据类型(数字、str),构造(String)。

二者之间唯一的差异是,基本类型的初始化可以直接赋值,而繁芜类型则须要某种布局函数。

堆与栈

我之前险些不须要考虑堆与栈的问题。
(据我所知,JS中的工具都存储在堆中,只有基本类型在栈中。

堆:

● 速率慢

● 比较大

● 非常快

● 比较小

基本类型和基本的构造都存储在栈中。
要在堆中存贮值,须要利用Box<T>。
其余,Vec<T> 也可以将值保存到堆中。

如果你利用的内存较多,或者须要在构造中利用带有值的enum,则可能须要利用堆。

如果发生栈溢出,则解释你利用了过多的栈内存。
对付一些较大的值,该当利用Box。

常见的基本类型

数字:

● i8、i16、i32、i64、i128:有符号整数,包括负数。
数字表示值的比特数。

● u8、u16、u32、u64、u128:无符号整数,从零开始。
它们的最大容量翻了一倍,由于有一个额外的比特可用(在有符号整数中用于表示符号)。
数字表示值的比特数。

● f32 和 f64:浮点数。
javascript 天下中常见的数字。

字符串:

● str:大略的UTF-8 字符串(所有 Rust 字符串都是 UTF-8。
不能利用无效的 UTF-8 字符串,会引发非常或造成panic)。
常日用作指针(即 &str)。

● String:一种更繁芜的类型(严格来说不是基本类型),存储在堆中。

数组:

● T[] :具有固定长度的数组(如果利用 Option<T> 类型,则数组内包含的元素数量可以小于实际长度)。

元组

元组可用于存储不同类型的多个值(从实质上来说便是可以容纳不同类型且大小固定的数组)。

与数组不同,元组可通过点(.)直接访问,例如 tuple.0 表示获取第一项,而 tuples 没有.len() 之类的方法。

let var = (1, \公众str\公众);

有一个很故意思的小技巧,你可以通过()(空元组)返回“void”。
既没有 return 语句,也不会返回值的函数会返回()。

常见构造

Option<T>

● 这是一个列举,值为Some(T) 或 None。
(我们稍后再谈论enum,Rust中的列举与其他措辞略有不同。

● 如果想获取该值,你可以利用 match,就像利用其他列举一样,或者利用 .unwrap() (如果值为None,则会导致panic)。

Result<T, E>

● 这个构造与 Option 类似,但常用于处理缺点(常日由 IO 方法返回)。

● 它的值是 Ok(T) 或 Err(E)。

● 如果想获取该值,你可以利用match 块或 unwrap()。

● 为了方便利用,当函数返回 Result<T, E> 时,可以在返回值为 Result<T, E>(个中E必须为兼容的类型)的方法调用之后利用 ? 来返回缺点E(类似于利用.unwrap(),但当函数出错时不会造成panic)。

fn example() -> Result<(), Error> { // 一种缺点类型。
为了简便起见,你可以利用String,或自定义enum。
something_that_returns_result()?; Ok(()) // returns empty Tuple}

Vec<T>

● 向量是可增长的数组,存储在堆上。

● 向量支持 .push()、.pop() 等常用操作。
详情拜会Rust文档。

Box<T>

● 在堆上存储T。
可用于在构造中利用enum,或者用于开释栈空间。

定义构造

构造类似于工具,但它们的大小是静态的。

构造可以通过如下几种办法定义。

● 利用Tuple作为声明(类似于元组的别名)

struct Something(u8, u16); // a struct with 2 numbers, one unsigned 8 bit, the other one unsigned 16 bit

● 利用工具表示法(类似于声明类或工具)

struct Something { value: u8, another_value: u16}

● 利用struct作为别名

struct Something = u8; // a single value

这种方法的适用情形为:你试图创建一个enum,而其值可能是正在定义的构造,而该构造中又要(直接或间接)引用该enum。

struct MaybeRecursive { possibly_self: Option<MaybeRecursive> // error!}struct MaybeRecursive { possibly_self: Option<Box<MaybeRecursive>> // fine}

我在为自己的shell创建抽象语法树时,就碰着了这个问题。

要创建构造的实例,须要利用下述写法(类似于C#中定义数组):

Something { variable: 1, another_variable: 1234}

定义enum

下面是示例:

enum EnumName { First, Second}

可以为enum指天命值(例如序列化或反序列化数值的情形):

enum EnumName { First = 1, Second // auto incremented}

更强大的写法如下:

enum EnumName { WithValue(u8), WithMultipleValues(u8, u64, SomeStruct), CanBeSelf(EnumName), Empty}

你可以用match提取出值。

Match

match是Rust最强大的功能之一。

Match是更强大的switch语句。
利用方法与普通的swtich语句一样,除了一点:它必须覆盖所有可能的情形。

let var = 1;match var { 1 => println!(\"大众it's 1\"大众), 2 => println!(\"大众it's 2\"大众), // following required if the list is not exhaustive _ => println!(\"大众it's not 1 or 2\公众)}

也可以match范围:

match var { 1..=2 => println(\公众it's between 1 and 2 (both inclusive)\"大众), _ => println!(\"大众it's something else\"大众)}

也可以什么都不做:

match var { _ => {}}

可以利用match安全地unwrap Result<T, E>和Option<T>,以及从其他enum中获取值:

let option: Option<u8> = Some(1);match option { Some(i) => println!(\公众It contains {i}\"大众), None => println!(\"大众it's empty :c\公众) // notice we don't need _ here, as Some and None are the only possible values of option, thus making this list exhaustive}

如果你不该用i(或其他值),Rust会发出警告。
你可以利用_来代替。

match option { Some(_) => println!(\"大众yes\公众), None => println!(\"大众no\公众)}

match也是表达式:

let option: Option<u8> = Some(1);let surely = match option { Some(i) => i, None => 0}println!(\"大众{surely}\公众);

你可以看看Option的文档(或通过IDE的自动补齐,看看都有哪些可以利用的trait或方法)。

你大概把稳到了,你可以利用.unwrap_or(val)来代替上述代码(上述match等价于.unwrap_or(0))。

Loop

loop循环是最大略的循环。
只须要利用loop即可。

loop { if something { break }}

该代码会一贯运行,直到碰着break(或return,return也会同时返回父函数)。

for

for循环是最大略易用的循环。
它比传统的for循环更随意马虎利用。

for i in 1..3 {} // for(let i = 1; i < 3; i++) // i++ is not a thing, see things to notefor i in 1..=3 {} // for(let i = 1; i <= 3; i++)for i in 1..=var {} // for(let i = 1; i <= var; i++)for i in array_or_vec {} // for(let i of array_or_vec) in JS// again, as most other things, uses a trait, here named \"大众iterator\"大众// for some types, you need to call `.iter()` or `.into_iter()`.// Rust Compiler will usually tell you this.for i in something.iter() {}

while

很大略的循环。
与其他措辞不同,Rust没有do...while,只有最根本的while。

while condition { looped();}

语法与if一样,只不过内容会循环实行。

打印输出

打印输出可以利用 print! 和 println!。

!表示这是一个宏(即可以扩展成其他代码的快捷办法),但你不须要过多考虑。
另一个常用的宏是 vec![] ,它能利用数组创建 Vec<T> (利用 [] 内的值)。

这些宏都有一个大略的模板系统。

● 输出一行利用 println!()。

● 输出一个静态字符串利用 print!(\"大众something\"大众)。
println!中ln的意思是行,也便是说它会添加换行符号(\n)。
console.log会默认添加换行。

● 要输出一个实现了Display trait的值(绝大多数基本类型都实现了),可以利用 print!(\"大众{variable}\公众)。

● 要输出一个实现了Debug trait的值(可以从Display继续),利用 print!(\公众{variable:?}\"大众)。

● 要输出更繁芜的实现了Display trait的内容,利用 print!(\"大众{}\"大众, variable)。

● 要输出更繁芜的实现了Debug trait的内容,利用 print!(\公众{:?}\"大众, variable)。

Trait

Trait是Rust中最难明得的观点之一,也是最强大的观点之一。

Rust没有采取基于继续的系统(面向工具的继续,或JavaScript基于原型的继续),而是采取了鸭子类型(即,如果一个东西像鸭子一样叫,那么它便是鸭子)。

每个类型都有且只有一个“默认”(或匿名)trait,只能在与该类型同一个模块中实现。
常日都是该类型独占的方法。

其他的都叫trait。
例如:

trait Duck { fn quack(&self) -> String; /// returns if the duck can jump fn can_jump(&self) -> bool { // default trait implementation. Code cannot have any assumptions about the type of self. false // by default duck cannot jump }}struct Dog(); // a struct with empty tupleimpl Dog { // a nameless default trait. fn bark(&self) -> String { String::from(\公众bark!\"大众) }}impl Duck for Dog { // implement Duck trait for Dog type (struct) fn quack(&self) -> String { String::from(\"大众quark!\公众) } // dog kind of quacks differently}let dog = Dog {};dog.bark();dog.quack();

首先,我们定义了trait(在面向工具措辞中叫做接口,但它只包含方法或函数)。
然后为给定的类型(上例中为Dog)实现trait。

一些trait可以自动实现。
常见的例子便是Display和Debug trait。
这些trait哀求,构造中利用的类型必须要相应地实现Display或Debug。

#[derive(Display,Debug)]struct Something { var: u8}println!(\"大众{:?}\公众, Something { var: 1 });

浸染域

Trait有浸染域,而且与它实现的类型的浸染域是独立的。
也便是说,你可以利用一个类型,但无法利用一个trait的实现(例如,如果这个实现来自其余一个库,而不是来自该类型本身)。
你可以use这个实现。

self

trait中的self指向它实现的类型。
&self是指向 self: &Self 的别名,个中Self表示该类型(上例中的 self: &Dog)。
self也是self: Self的别名,但两者的差异便是后者会移动变量(即花费该变量,该变量就无法从外部访问了)。

当函数定义不以self、&self或&mut self开始时(&mut self相称于带有可改变引用的 &self),便是一个静态方法。
Trait依然可以像任何方法一样定义并实现静态方法。
常见的一个静态方法是new,用于创建类型或构造的实例:

impl Something { fn new() -> Something { Something { x: 1 } }}...let var = Something::new();

指针

指针实际上非常易懂,只管它来自其他更高等的措辞。
我常常会用错。

&A指向A,利用时只须要确保A存在,即可担保&A存在,由于我们不应该让指针指向不存在的工具。

Rust会在编译时进行静态检讨,确保不会涌现上述情形。
它会自动开释超出浸染域的变量,并且不许可指针的存活超过变量。
另一个安全担保是,只能有一个可改变的指针。

也便是说下述代码是缺点的:

let a = 1;let b = &a;let c = &mut a;println!(\"大众{b}\"大众); // Error! there can only be one mutable pointerc = 1;

我们只须要担保原始变量在指针的浸染域中一贯存在即可。

在构造中利用指针会有点问题,由于编译器不喜好这种做法(由于构造的寿命常日比原始变量更长)。
我常日会采取所有权转移或克隆(.clone(),Clone trait的一部分,可以被derived)。

有时候,一些函数哀求只能用指针,不能用所有权转移。
这时,只需在值的前面加上 & (或 &mut)即可。

something(&a);

此外,还有双重、三重等指针,但很少见,而且一样平常来说只会更难处理。

你也不须要考虑开释变量的问题,Rust会在超出浸染域时自动开释。

命名空间

利用全名就无需导入。
导入只不过是别名。

std::env::args()

use std::env;env::args()

use std::env::args;args()

选择多个“命名空间”可以利用{},如:

use std::env::{args, var};

也可以重复利用use:

use std::env;use std::env::args;env::var();args()

还有一点,你也可以在函数内利用use。
这样,如果代码没有被实行,库就不会被导入(即,如果函数没有在代码路径中涌现,例如,use了一个测试用的库,而use只写在了测试用例中,那么在正常构建时就不会导入该库)。

fn test() { use std::env; env::var();}

但我不推举在正常的代码路径中这样写,该当利用全局的导入。

可见性

谈论完命名空间之后,我们来谈论一下可见性。

实质上,默认情形下任何东西都是私有的,只能被它所在的文件访问。

● trait及其方法

● 构造及其成员

● enum(成员继续enum的可见性,这是合理的,拜会Match)

● 函数

● trait的实现依赖于trait和实现该trait的构造,即,只有两者都是公有的,该实现才是公有的。

要设置为公有(即可以从外部访问),须要利用关键字pub:

pub struct Something { pub letter: char}pub trait CustomTrait { ... }pub fn method() {}

利用多个文件

有时候我会惦记 require(\"大众./fire\"大众)。

要想“导入”一个文件,要利用 mod指令。
通过cargo下载的crate会自动导入。

main.rs

mod my;fn main() { my::function(); // or use my::function; function();}

my.rs

pub fn function() { println!(\"大众function\"大众);}

你也可以利用pub mob重新导出一个文件。
绝大多数已有的Rust代码都会对支持文件夹采取下列操作:

main.rs

mod my;use my::file;fn main() { file::function();}

my/mod.rs - mod.rs这个名字是分外的,类似于index.js

pub mod file;

my/file.rs

pub fn function() { println!(\"大众function\公众);}

关于println!,拜会“打印输出”。

编写文档

编写文档只需利用三个斜线 ///。
一些IDE会采取不同的高亮办法显示。

类似于JSDoc,只不过其类型不会显式标注,由于代码中已经写了类型。

/// a description of varlet var = \"大众something\公众;

原文地址:https://danbulant.eu/posts/rust-basics

END

《新程序员001-004》全面上市,对话天下级大师,宣布中国IT行业创新创造

造诣一亿技能人

标签:

相关文章