Rust 中的可变性和与面向对象语言的区别

这篇文章一开始是对一个stackoverflow问题的冗长回答。这个问题让我意识到,当来自像c#和Java这样的流行语言时,对于可变性有一种非常普遍的思考方式,而这种思维方式一旦生锈,往往会导致困惑和沮丧。

我将以一种(希望是)直观地表达差异的方式,对涉及到易变性的常见问题的解决方案进行比较和对比。对于不锈的示例,我将使用Java,但是这些示例也可以用其他面向对象的语言进行类似的表达。

我们将从一个简单的目标开始,定义一个狗输入两个字段:

  • 名称,这将是不可变的
  • 年龄,这将是可变的

在前几个示例中,为了避免使事情变得简单,我们将避免使用诸如getter和setter之类的通用习语。

这是我们在Java中定义类型的方法:


class Dog {
    public final String name;
    public int age;

    public Dog(String name) {
        this.name = name;
        this.age = 0;
    }
}

rust的等效类型如下所示:

struct Dog {
    pub name: &'static str,
    pub age: usize,
}

impl Dog {
    pub fn new(name: &'static str) -> Dog {
        return Dog { name, age: 0 };
    }
}

在rust示例中,您可能会注意到我们的名称字段以表明我们以任何方式控制可变性。 这是因为rust在等级领域没有可变性的概念。

Rust采取了根本不同的方法。 如果要修改类型上的任何字段,则需要声明一个指向它的可变变量。 默认情况下,所有变量都是不可变的,您必须添加mut修饰符,如果您打算对其进行突变:

// Declare an immutable variable
let harris = Dog::new("Harris");
harris.age += 1; // Compile error!

// Declare a mutable variable
let mut harris = Dog::new("Harris");
harris.age += 1; // No problem!

乍一看,似乎Java提供了比rust更好的对可变性的控制,而不是rust。如果变量是可变的,我们就可以改变其所有字段。 如果它是不可变的,我们就不能对其进行任何突变。

可变性和self参数

让我们更新示例来让它们更加惯用。 我们将字段设为私有,并公开用于访问或修改它们的方法。

Java的第一个:


class Dog {
    private final String name;
    private int age;

    public Dog(String name) {
        this.name = name;
        this.age = 0;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public void incrementAge() {
        this.age += 1;
    }
}

还有我们的rust示例:

struct Dog {
    name: &'static str,
    age: usize
}

impl Dog {
    pub fn new(name: &'static str) -> Dog {
        return Dog { name, age: 0 };
    }

    pub fn get_name(&self) -> &'static str {
        return self.name;
    }

    pub fn get_age(&self) -> usize {
        return self.age;
    }

    pub fn increment_age(&mut self) {
        self.age += 1
    }
}

在rust示例中,get_name,get_age和年龄方法都包括自 parameter. Like variables,parameters are either mutable or immutable. 自传递给方法的方式与其他参数不同:如果我们有一个名为dog的变量,并调用dog.increment_age(),则dog变量作为自参数隐式地传递给increment_age方法,并且可以访问在类型上声明的私有字段。

当我们考虑将其与变量结合使用时,这一点尤其有趣。 如果我们有一个不变的变量,并且我们在它上面调用了一个方法,该方法需要一个&mut self,我们会遇到一个编译错误,因为该方法要求可变地获得我们的价值!

// Declare an immutable dog variable
let dog = Dog::new("Harris");
dog.increment_age(); // Compile error!

// Declare a mutable dog variable
let mut dog = Dog::new("Harris");
dog.increment_age(); // This works!

因此,如果我们将变量声明为可变的,并调用一个采用不可变的方法,会发生什么情况?自参数? 在这种情况下,该调用是有效的,但是该方法仍然仅被授予对自参数。

重申:为了调用需要可变访问的方法自,变量必须被声明为可变的,但是声明不可变的方法自参数可以通过可变和不变的变量来调用。

返回类型中的编码可变性

我们将引入一个新的领域来说明可变性如何与更复杂的类型一起工作。 我们将添加一个清单(在rust中的Vec)朋友们:


class Dog {
    private final List<Dog> friends = new ArrayList<>();
    ...

    public List<Dog> getFriends() {
        return this.friends;
    }

    public void addFriend(Dog friend) {
        this.friends.add(friend);
    }
}

在Java示例中,我们的新朋友们字段已被声明为final,这意味着我们永远无法更改该字段指向的内容,然而,我们仍然可以通过我们的getter方法自由地从列表中添加和删除项目,如下所示:


   Dog harris = new Dog("Harris");
   Dog buck = new Dog("Buck");
   harris.getFriends().add(buck);

那么如何在rust中工作呢?让我们更新一下我们的例子:

struct Dog {
    ...
    friends: Vec<Dog>,
}

impl Dog {
    ...
    pub fn get_friends(&self) -> &Vec<Dog> {
        return &self.friends;
    }
}

在我们的rust示例中,我们实际上是将可变性编码到get_friends方法的返回类型中!&Vec<Dog>返回类型表示不可变引用,因为它缺少mut关键字。如果我们试图将一个项目添加到返回的朋友列表,这将是一个编译错误:

let mut harris = Dog::new("Harris");
let buck = Dog::new("Buck");
harris.get_friends().push(buck); // Compile error!

让我们分解一下:

  • 我们声明了一个可变harris变量
  • get_friends方法返回对我们字段的不变引用friends
  • VEC类型有一个名为方法push这需要一个可变引用self
  • 我们的代码无法编译,因为get_friends返回的不可变引用不能用于调用需要对self进行可变访问的方法

因此,即使我们将原始变量声明为可变的,我们也会使用get_friends将访问权限隐藏在不可变引用后面的方法。 因此,如果我们尝试对其进行突变,则会导致编译错误。

作为一个练习,假设我们希望get_friends方法提供对返回的friends引用的可变访问。花点时间想想我们需要改变什么。

有想法吗 答案如下:

pub fn get_friends(&mut self) -> &mut Vec<Dog> {
        &mut self.friends
    }

总而言之,我们必须:

  • 将我们的self参数声明为可变的,
  • 将返回类型声明为可变的,并且
  • 返回对我们列表的可变引用

我们现在可以修改friends 通过我们列出get_朋友们方法! 我们是否愿意在实践中这样做是另一个问题。

结论

Rust的突变性与大多数人所习惯的非常不同,它可能需要一段时间才能被人们理解。然而,一旦它做到了,它就变成了一种令人难以置信的强大的方式来执行在大多数语言中不可能实现的保证。它也是语言的基本部分,与其他语言特性协同工作,实现安全性和性能的独特结合。

原文链接: https://dev.to//dubyabrian/mutability-in-rust-and-how-it-differs-from-object-oriented-languages-32e

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值