从PHP到JS:我的编程语言学习之旅与经验分享

作者 |丹尼尔·布兰特

从PHP到JS:我的编程语言学习之旅与经验分享

译者|新月

出品| CSDN(ID:CSDNnews)

当我第一次开始编程时,我使用的是 PHP。就我个人而言,我觉得PHP的语法有点笨拙和不自然,有时甚至很烦人(为什么我必须在每个变量前面加上$前缀?常量前面没有$不是仍然有意义吗?)在学习了其他之后语言,我不再喜欢 PHP,但是 PHP 的一些东西我还是喜欢的。例如,数组循环很容易,并且有多种编程范式:函数式、面向对象、特征等。

后来我又学了JS,它和C语言很相似,随处可见。在此期间,我也参与了一些Java和C#项目,但后来又回到了JS。

我也尝试过学习 C(和 C++),虽然从 Sololearn 获得了证书,但我从未真正使用过这两种语言,它们看起来很复杂:快速访问内存很酷,但为什么我必须使用 free?为什么它不知道超出范围时应该自动释放内存?

因此,我还是更喜欢使用JS编程,因为不需要考虑内存问题。而且,IO相关的操作现在并没有限制V8的速度。

后来我听说了Rust,一种Mozilla开发的语言。它连续多年位列 StackOverflow 上最喜欢的编程语言榜单,甚至超过了我非常喜爱的 Typescript(我喜欢 Typescript 的原因主要是因为类型安全)。所以,我想我应该找个机会尝试一下。

学习资源

我面临的困难之一是寻找易于理解且简短的优质资源。我不喜欢YouTube视频,我更喜欢在上下班途中快速浏览一些文档或阅读一些学习资源,而不消耗大量数据。

这是我找到的资源列表:

●《Rust 编程语言》(

):这是一本在线书籍,描述了可以用 Rust 实现的最常见的功能。

●“Rust 简介”(

):一本简短的书,可以在一两个小时内读完,然后用一两天的时间尝试示例。文章涵盖的内容比较深入,但很容易掌握。

●

:这是一个reddit社区(如果遇到比较复杂的问题,可以在这里发帖,等待别人解答。)

● Discord 社区:您可以通过该社区向其他开发人员询问有关 Rust 的信息。

● Rust 实例(

):介绍了一些例子,可以作为入门的首选书籍。

入门

请参阅 Rust 网站(

),只需使用 rustup 即可。

如果你想创建一个新项目,运行cargo init(无需指定是否在空目录中)。

然后就可以从src/main.rs开始编写了。

与C类似,主程序被包装在main中。不同之处在于它不接受任何参数,也不应该返回整数,这些函数应该使用命名空间 std::env。

另外,我建议使用 CLion 并安装 Rust 扩展。 VSCode 也有 Rust 扩展,但性能相比较差。当然,您可以使用其他 JetBrains 编辑器,但 CLion 具有其他编辑器所没有的一些本机功能(例如调试)。拥有 GitHub Education Pack 的学生可以免费使用该插件。

有趣的笔记

不仅是变量,还可以在函数和特征内部使用嵌套函数和用途。这些无法从外部访问,如果不使用,也不会出现在代码中。至少我是这么认为的。

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

结构体(和其他类型)、枚举(包括枚举值)和特征(但不是它们的函数/方法)需要以大写字母开头,并且不能包含任何下划线。

实际上,您可以使用 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。

另外,运算符重载使用了特征,但本文不打算详细讨论它们。

除了函数调用之外,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 something {} else {} else if something_else {}

if语句的使用非常基础,这里不再详细介绍。

需要注意的一件事是,除非必要,否则使用括号实际上是一种错误。您可以使用括号指定执行顺序:

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

前面提到,if 也可以返回一个值,该值可以用在赋值、参数、返回或其他地方。

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

这里的花括号是必需的。

类型

Rust 有两种类型:基本数据类型(数字、str)和结构体(字符串)。

两者之间的唯一区别是基本类型的初始化可以直接完成,而复杂类型则需要某种构造函数。

堆和栈

我以前几乎不需要考虑堆和堆栈。 (据我所知,JS中的对象存储在堆中,只有基本类型存储在堆栈中。)

堆:

● 慢

● 比较大

堆

● 非常快

● 相对较小

原始类型和原始结构存储在堆栈中。要在堆中存储值,需要使用Box。另外,Vec还可以将值保存到堆中。

如果您使用大量内存,或者需要在结构中使用带有值的枚举,您可能需要使用堆。

如果发生堆栈溢出,则说明您使用了过多的堆栈内存。对于一些较大的值,应使用 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 类型,则数组包含的元素个数可以小于实际长度)。

元组

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

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

let var = (1, "str");

一个有趣的小技巧是你可以通过 () 返回“void”(一个空元组)。既没有 return 语句也没有值的函数 returns ()。

共同结构

选项

● 这是一个值为Some(T) 或None 的枚举。 (我们稍后会讨论枚举,Rust 中的枚举与其他语言略有不同。)

● 如果您想获取该值,您可以使用match,就像任何其他枚举一样,或者使用.unwrap()(如果该值为None,这将导致panic)。

结果

● 该结构与Option 类似,但常用于处理错误(通常由IO 方法返回)。

● 取值为Ok(T)或Err(E)。

● 如果要获取值,可以使用match 块或unwrap()。

● 为了方便使用,当函数返回Result 时,可以使用?在调用返回 Result 的方法后返回错误 E(其中 E 必须是兼容类型)(类似于使用 .unwrap(),但当函数出错时不会引起恐慌)。

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

向量

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

● Vector 支持.push()、.pop()等常用操作。有关详细信息,请参阅 Rust 文档。

盒子

● 将T 存储在堆上。可用于在结构中使用枚举,或释放堆栈空间。

定义结构

结构与对象类似,但它们的大小是静态的。

结构可以通过多种方式定义:

● 使用Tuple作为声明(类似于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

当您尝试创建一个枚举,其值可能是定义的引用该枚举(直接或间接)的结构时,此方法非常有用。

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

我在为 shell 创建抽象语法树时遇到了这个问题。

创建结构体实例需要使用如下写法(类似于C#中定义数组):

Something { variable: 1, another_variable: 1234}

定义枚举

这是一个例子:

enum EnumName { First, Second}

您可以为枚举指定数值(例如,序列化或反序列化数值时):

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

更强大的写法如下:

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

您可以使用 match 来提取值。

匹配

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

Match 是一个更强大的 switch 语句。用法与普通switch语句相同,只不过它必须涵盖所有可能的情况。

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 var { 1..=2 => println("it's between 1 and 2 (both inclusive)"), _ => println!("it's something else")}

你也可以什么都不做:

match var { _ => {}}

您可以使用 match 来安全地解包 Result 和 Option,以及从其他枚举获取值:

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 的自动完成功能来查看可以使用哪些特征或方法)。

您可能已经注意到,您可以使用 .unwrap_or(val) 代替上面的代码(上面的匹配相当于 .unwrap_or(0))。

环形

循环是最简单的循环。只需使用循环即可。

loop { if something { break }}

该代码将一直运行,直到遇到break(或return,它也返回父函数)。

为了

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() {}

尽管

非常简单的循环。与其他语言不同,Rust 没有 do...while,只有最基本的 while。

while condition { looped();}

语法与 if 相同,只不过内容将在循环中执行。

打印出

打印输出可以使用print进行打印!和打印!。

!表明这是一个宏(即可以扩展为其他代码的快捷方式),但你不需要考虑太多。另一个常用的宏是 vec![],它从数组创建 Vec(使用 [] 内的值)。

这些宏有一个简单的模板系统。

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

● 要输出静态字符串,请使用print!("something")。在 println 中!表示line,表示会添加换行符(\n)。 console.log 默认会添加换行符。

● 要输出实现 Display 特征的值(大多数基本类型都实现它),您可以使用 print!("{variable}")。

● 要打印实现 Debug 特征(可以从 Display 继承)的值,请使用 print!("{variable:?}")。

● 要打印实现 Display 特征的更复杂的内容,请使用 print!("{}", variable)。

● 要输出实现Debug 特征的更复杂的内容,请使用print!("{:?}",variable)。

特征

Traits 是 Rust 中最难理解的概念之一,也是最强大的概念之一。

Rust 没有采用基于继承的系统(面向对象的继承,或 JavaScript 的基于原型的继承),而是采用鸭子类型(即,如果某物像鸭子一样嘎嘎叫,那么它就是鸭子)。

每种类型都有一个且仅有一个“默认”(或匿名)特征,该特征只能在与该类型相同的模块中实现。通常方法是该类型特有的。

其他一切都称为特质。例如:

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();

首先,我们定义特征(在面向对象语言中称为接口,但它们只包含方法或函数)。然后实现给定类型(上例中的 Dog)的特征。

有些特征可以自动实现。常见的示例是显示和调试特征。这些特征要求结构中使用的类型必须相应地实现 Display 或 Debug。

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

范围

特征具有范围,并且独立于它们实现的类型的范围。也就是说,您可以使用类型,但不能使用特征的实现(例如,如果实现来自另一个库而不是类型本身)。您可以使用此实现。

自己

特征中的 self 指向它实现的类型。 &self 是指向 self: &Self 的别名,其中 Self 代表类型(上例中的 self: &Dog)。 self也是self的别名:Self,但两者的区别在于后者移动变量(即当变量被消费时,外部无法访问该变量)。

当函数定义不以 self、&self 或 &mut self 开头(&mut self 相当于带有可变引用的 &self)时,它是静态方法。 Traits 仍然可以像任何方法一样定义和实现静态方法。常见的静态方法是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 特征的一部分,可以派生)。

有时,某些函数要求只能使用指针而不能使用所有权转移。在这种情况下,只需在值前面添加 &(或 &mut)即可。

something(&a);

此外,还有双指针、三指针等,但它们很少见,而且一般只是比较难处理。

你也不需要考虑释放变量,当它们超出范围时,Rust 会自动释放它们。

名称空间

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

std::env::args()

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

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

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

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

您也可以重复使用它:

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

另一件事是,您还可以在函数内部使用 use 。这样,如果代码没有执行,则不会导入该库(即该函数没有出现在代码路径中,例如使用了一个测试库,use只写在测试用例中) ,那么在正常构建中不会导入库)。

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

但我不建议在正常的代码路径中编写此代码。您应该使用全局导入。

能见度

现在我们已经讨论了命名空间,让我们讨论可见性。

本质上,默认情况下所有内容都是私有的,并且只能由其所在的文件访问。

● 特征及其方法

● 结构及其成员

● enum(成员继承了enum的可见性,这是​​合理的,参见Match)

● 功能

● 特征的实现取决于该特征以及实现该特征的结构。也就是说,只有当两者都是公开的时,实现才是公开的。

要使其公开(即从外部访问),请使用关键字 pub:

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

使用多个文件

有时我会想念 require("./fire")。

要“导入”文件,请使用 mod 命令。通过 Cargo 下载的板条箱将自动导入。

主程序.rs

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

my.rs

pub fn function() { println!("function");} 

您还可以使用 pub mob 重新导出文件。大多数现有 Rust 代码对支持文件夹执行以下操作:

主程序.rs

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

my/mod.rs - mod.rs 这个名字很特殊,类似于index.js

pub mod file;

我的/文件.rs

pub fn function() { println!("function");}

对于 println!,请参阅“打印输出”。

编写文档

要编写文档,只需使用三个斜杠 ///。一些 IDE 使用不同的突出显示方法。

与 JSDoc 类似,只不过它的类型不会被显式标记,因为类型已经写在代码中了。

/// a description of varlet var = "something";

你可能想看:
« 上一篇
下一篇 »

相关推荐

java中什么是冒泡,冒泡算法

2026年09月06日

27阅读

java中冒号,java双冒号

2026年09月06日

26阅读

java中的->,java中的是什么意思

2026年09月06日

24阅读

java中的super用法,Java中super的说明

2026年09月06日

28阅读

java中反射的应用,java中反射的应用场景

2026年09月06日

26阅读

java中开关的定义,Java关键字有哪些

2026年09月06日

26阅读