从一个 PHP 开发者的角度来看 Rust
我第一次开始学习 Rust 大约在 6 个月前,当时我正在寻找一门新的语言学习时,我遇到了它。刚开始的时候我以为 Rust 是一门底层系统编程语言,但随着我学习的深入,我越发意识到它具有成为一门高级编程语言和编写 Web 应用的潜力。 此外,在学习的过程中,我还学习了许多 Rust 可以避免像许多其他编程语言编写的应用程序中经常出现的典型的 bug 的方法。
让我们来研究一下用 Rust 编写 web 应用程序所带来的一些好处。
强类型
在 Rust 中,所有内容都是强类型的,并在编译时进行检查。例如,如果程序中的函数接受 String 类型的参数,并且程序编译成功,则可以保证函数所接受的值只有字符串。因此,您可以避免编写在运行时检查参数的类型是否正确的防御性代码,或者参数是否为 null 的防御性代码。这使程序运行得更快,消除了与类型相关的错误的可能性,并使您编写的代码更少。
pub fn generate_greeting(name: String) -> String
{
format!("Hi {}!", name)
}
pub fn main()
{
let greeting = generate_greeting(String::from("Diego"));
println!("{}", greeting);
}
甚至更进一步,它还强制了表达式中使用的值的类型,例如 if
语句。在其他语言中,通常可以使用非布尔类型(如整数或字符串)作为 if
语句的条件,
当在这种情况下,这通常会导致 Rust 出现意外的行为。在 Rust 中你必须始终提供一个boolean
类型,这样可以确保结果是程序员的意图,Rust不会根据某些内部因素或程序员可能熟悉或不熟悉的规则来猜测某些内容是假的还是真的,以下代码将不会在Rust中编译:
pub fn main()
{
let number = 0;
if number {
println!("number is not falsy!");
}
}
编译器将显示以下相当有用的错误信息:
6 | if number {
| ^^^^^^ expected bool, found integral variable
默认不可变
在我看来,在 Rust 中编写的另一个好处是,默认情况下,一切都是不可变的。这可以限制您在明确的情况下使某些需要改变的内容被允许改动,从而防止出现某些数据在您不打算改变时发生改变的意外情况。
看下面这个例子:
pub fn walk_dogs(dogs: &Vec<&str>)
{
for dog in dogs {
println!("Walking a {}...", dog);
}
}
pub fn main()
{
let dogs_collection = vec!["German Spitz", "Golden Retriever", "Akita"];
walk_dogs(&dogs_collection);
//这一直会是 true,因为 dogs_collection 变量是可变的。
assert_eq!(dogs_collection, vec!["German Spitz", "Golden Retriever", "Akita"]);
}
事实上,我们可以将我们的狗的集合传递给一个方法,并保证数据根本没有改变,对于我来说这是令人兴奋的事情,并成为 Rust 的一个令人上瘾的功能。很明显,在上面的例子中 walk_dogs
方法不会改变我们的集合,但是如果它是来自另一个库的方法呢?那么我们是否需要确保数据被函数调用后没有发生改变?即使该方法现在没有改变它的值,但是我们无法知道在未来版本的库中会不会被实现。
另一方面,我们可以放心地使用 Rust,它的方法不会改变数据,因为我们正在将一个不可变的引用传递给 dogs_collection
向量。如果 walk_dogs
方法试图以任何方式改变此数据,此代码将无法编译,因为它会破坏 Rust 的所有权规则。所有权规则是使 Rust 语言变得独特的特性之一,你可以在 Rust 手册中阅读与这些特性相关的更多信息。
但是,如果我们想让函数来改变数据,那么函数签名和调用函数的代码都必须通过显式地传递和接受可变引用,来明确改变数据的意图。如下例所示:
pub struct Product
{
name: String,
price: f32,
stock: i32
}
pub fn purchase_product(product: &mut Product, qty: i32)
{
// 省略采购逻辑
product.stock = product.stock - qty;
}
pub fn main()
{
let mut product = Product { name: String::from("Green Armchair"), price: 150.50, stock: 30};
purchase_product(&mut product, 5);
assert_eq!(product.stock, 25);
}
在这里你可以看到我们通过使用 mut
关键字就可以立即定义变量 product
为可变的,这告诉 Rust 我们打算改变这个变量,这引入了一些限制,例如我们一次只能对一个值进行可变引用,从而保护您的代码免受数据竞争冲突。 注意,purchase_product
函数签名还必须使用 &mut Product
语法明确指定它接受对Product 的可变引用,编译器会确认这种情况。最后我们可以看到 product.stock
属性在函数内部发生了改变,订购的数量已被减去,产品 product
对象的库存 stock
现在为 25。
枚举和模式匹配
枚举和代数数据类型在 Rust 中广范使用。用下面的例子来说明枚举和完备的模式匹配的威力。我最喜爱的 Rust 设计之一就是它没有 null
类型。Rust 中没有变量可以为空类型。那如何表示值缺失呢?你猜到了,就是枚举!
pub enum Option<T> {
None,
Some(T),
}
事实上,Rust 中用 Option
表示数据的缺失和存在,注意到 Option
还是一个泛型。表示没有值时 Option
为 None
, 表示有值时为 Some
。
看看下面的例子是如何使用 Option
枚举的:
pub struct Customer {
id: i32,
name: String
}
pub fn get_customer_by_id(id: i32) -> Option<Customer>
{
//从数据库获取客户信息
}
pub fn main()
{
let customer = get_customer_by_id(32);
}
这里我们有一个 Customer
结构,把它想象成其他语言的类,比如 PHP 和从 DB 加载客户 customer
的函数。具有给定 id
的客户 customer
不存在于 DB
中的可能。因此,我们需要返回选项以允许不存在客户的可能性。现在,我们不能只假设 get_customer_by_id
返回的客户 customer 是 Customer
的一个实例,如果我们尝试这样做,我们的程序就不会编译,编译器不仅不允许你假设你有一个 Customer 的实例,它还会强制你完整处理函数返回的及枚举的每个可能的变量,从而使您的代码健壮。
pub struct Customer {
id: i32,
name: String
}
pub fn get_customer_by_id(id: i32) -> Option<Customer>
{
Some(Customer {id: 32, name: "Diego".to_string()})
}
pub fn main()
{
let customer = get_customer_by_id(32);
match customer {
Some(a) => println!("Customer name: {}", a.name),
None => println!("Customer not found")
}
}
使用匹配运算符,我们能够处理调用 get_customer_by_id
函数的每个可能的结果。请注意,我们可以从枚举中提取数据,在这个例子中,Customer
实例最终匹配在匹配表达式的第一个臂柄的变量 a
中,然后我们可以在 =>
的右侧使用它。
大多数编程语言都不会强制检查变量的值,大量的代码漏洞是由于假设函数返回某种类型的值,但有时却会返回另一种类型。如果你不检查所有可能的类型 Rust 类型系统会有你的后门,而编译器将不允许你编译你的代码。
更多
这里我只是接触一点表皮,Rust 还有很多特征帮助你编译更快更安全的应用。如果:
- 开箱即用的依赖管理
- 相当好的模块系统
- 没有继承
- 特征
- 无畏的并发
- 等等
如果你喜欢这篇介绍,而且想开始学习 Rust , 你可以查看官方的 Rust Book. 你也可以看看 Web 相关的库和框架在 Are we web yet?
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: