【外评】用 Python 解释 Rust 背后的思想或理念

Rust 被宣传为内存安全的编程语言。 在学习 Rust 时,我发现一些教程和书籍都在强调生命周期、borrow 检查器以及不安全的 Rust 如何工作等细节。 学术界也有很多关于安全问题的论述(例如理论 borrow 模型)。

但对于普通的业务(或 CRUD)程序员来说,这些都太多了。 我不认为内存安全是 Rust 的特别之处。 那么,这些复杂概念背后的基本思想是什么? 难道就没有一种更简单的解释能让我们普通程序员理解吗?

其实,我认为 Rust 背后的理念非常简单。 在这篇文章中,我将尝试用 Python 来解释 Rust 背后的思想或理念……(为什么不呢? (为什么不呢?这很容易理解!)

假设我们正在编写一些商业应用程序,需要处理电子邮件。 但电子邮件可能很棘手。 我们必须确保它们是有效的。 一种方法是编写一个检查函数:

def check_email(email: str) -> bool:
    """my super genius email validation algorithm"""
    ...

在每个使用电子邮件的功能中,我们都会调用电子邮件检查器,以确保电子邮件有效:

def some_func_using_email(email: str) -> None:
    """
    probably send some spam to the email
    """
    if not check_email(email):
         raise ValueException("Invalid email!")

这可能行得通。 但也有缺点: 1) 有人可能会忘记调用检查器,因此可能会发生不好的事情。 2)如果必须在运行时不断检查电子邮件,成本太高。 那么我们该如何解决这个问题呢? 我们可以做的一件事就是编写更好的文档:

def some_func_using_email(email: str) -> None:
    """
    WARNING: EMAILS MUST BE VALID! CALL `check_email` BEFORE CALLING ME!
    OR ANYTHING COULD HAPPEN! I WARNED YOU!
    probably send some spam to the email.
    """
    # if not check_email(email):
    #     raise ValueException("Invalid email!")

这样调用者就可以谨慎行事……但你知道,这可能行不通。 如果你坚持向函数传递类似 “10fksja “这样的无效电子邮件,就会导致未定义行为(UB),因为函数作者告诉过你:它的域只包含有效电子邮件,对于无效电子邮件没有定义(想想 div 0,它根本就没有定义)。

上述方法相当于 Rust 中的 “不安全”:

# warning: invalid python syntax
unsafe def some_func_using_email(email: str) -> None:
    """
    probably send some spam to the email.

    `email` must be valid. Call `check_email` to ensure that.
    """
    # if not check_email(email):
    #     raise ValueException("Invalid email!")

只是在 Rust 中,如果有人调用 “不安全 “函数,编译器会提供一些保证,他们必须在一个 “不安全 “代码块或另一个 “不安全 “函数中调用该函数,以提醒你必须手动检查是否违反了该函数所要求的契约。 但我们可以做得更好。 我们可以将更多的负担转移给编译器。

问题是,电子邮件并不只是任意字符串! 我们可以定义一种电子邮件类型:

@dataclass
class Email:
    value: str


def some_func_using_email(email: Email) -> None:
    """
    probably send some spam to the email.
    """
    # if not check_email(email):
    #     raise ValueException("Invalid email!")

现在,我们可以让编译器(或 Python 的类型检查器)静态验证只有有效的电子邮件才会被传递给这些函数! 再也不用忘记调用检查器了,因为获取电子邮件实例的唯一方法就是调用检查器:

# warning: invalid python syntax
unsafe def check_email(email: str) -> Email:
    """my super complex email checking algorithm"""
    ...


def another_func(may_be_email: str) -> None:
    """check email and call `some_func_using_email`"""
    unsafe:
        email = check_email(may_be_email)
    some_func_using_email(email)

为什么要在 “check_email “函数中使用 “unsafe”? 因为编译器无法验证我们是否真的从任意字符串返回了有效的电子邮件。 我们必须手动检查我们的电子邮件验证算法是否正确。 我在这里加上 “不安全 “的意思是,我作为函数作者,保证返回的是一个有效的电子邮件实例。从现在起,每个人都可以放心,只需假定该电子邮件在传递时是有效的即可。

因此,我们的大部分代码都是 “安全 “的,而且效率更高,因为编译器会对其进行静态验证。 唯一 “不安全 “的部分是我们的 “check_email “函数,它的作用域非常有限,可以通过严格的检查和测试来确保其正确性。

就是这样。 这就是 Rust 背后的理念。 在 Rust 中,你随处可以看到这种模式。 例如,”[u8]”和 “str “有什么区别? 后者是有效的 UTF-8 编码,而前者是任意字节。 它们与 “Email “和 “str “非常相似。

内存安全怎么办? 在 Rust 中,我们将问题转换为类型检查,让编译器来完成这项工作。 我们给引用附加标签(生命周期参数,也是类型,意味着值集,根据借用检查器的解释,是内存位置或代码行号),这样它们就不再只是代表内存中特定位置的整数,而是编码了强制访问特定内存的合约额外信息的 “香料 “类型(就像普通 str 的 Email 类型,只是复杂得多)。

当然,如何做到这一点相当复杂,这就是学术研究发挥作用的地方。 许多研究语言都有这种功能,这并不是 Rust 所独有的。 对于像我们这样的最终用户来说,大多数时候我们并不在意。 内存安全只是设计模式的一个特殊用例。

那么并发安全呢? 我们有 “发送 “和 “同步 “标记特性,它们本身是 “不安全 “的,因此实现者必须确保植入它们的类型具有所需的语义,然后编译器就可以完成剩下的工作。

在我看来,这种将正确性检查转换为类型检查,并将负担卸载给编译器的理念正是 Rust 的精髓所在(这也反映了 Rust 在 ML 语言(如 F# 或 OCaml)中的根基)。 (对于我们无法转换为类型检查的问题,我们只需附上 “不安全 “字样,让程序员注意到,编译器会提供一些帮助。 其他语言可能会提供静态和严格的类型检查,但在 Rust 中,”不安全 “和 “安全 “代码的区分强制了对问题的认识,而上述设计模式的广泛使用(无处不在)则是 Rust 的真正独特之处。

我希望这些解释能帮助你掌握 Rust 的方式,而不会被所有细节所淹没。 这对我肯定有帮助。 顺便说一句,如今我在写 Python 时,感觉就像是在写带有 Python 口音的 Rust。

你也许感兴趣的:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注