Rust and Swift (xiii)

Methods, instance and otherwise.

February 28, 2016 (updated March 06, 2016)Filed under Tech#programming languages#rust#rust-and-swift#swiftMarkdown source

I am reading through the Swift book, and comparing it to Rust, which I have also been learning over the past few months. As with the other posts in this series, these are off-the-cuff impressions, which may be inaccurate in various ways. I’d be happy to hear feedback! Note, too, that my preferences are just that: preferences. Your tastes may differ from mine. (See all parts in the series.)


Rust and Swift both have methods which are attached to given data types. However, whereas Rust takes its notion of separation of data and functions rather strictly, Swift implements them on the relevant data structures (classes, structs, or enums) directly. In other words, the implementation of a given type’s methods is within the body of the type definition itself in swift, whereas in Rust it is in an impl block, usually but not always immediately adjacent in the code.

This goes to one of the philosophical differences between the two languages. As we’ve discussed often in the series, Rust reuses a smaller set of concepts—language-level primitives—to build up its functionality. So methods on a type and methods for a trait on a type are basically the same thing in Rust; they’re defined in almost exactly the same way (the latter includes for SomeTrait in the impl expression). In Swift, a method is defined differently from a protocol definition, which we’ll get to in the future. The point is simply this: the two take distinct approaches to the relationship between a given type definition and the implementations of any functions which may be attached to it.

Another important difference: access to other members of a given data type from within a method is explicit in Rust and implicit in Swift. In Rust, the first parameter to an instance method is always self or &self (or a mutable version of either of course), much as in Python. This explicitness distinction is by now exactly what we expect from the two languages.

Both use dot notation, in line with most other languages with a C-like syntax, for method calls, e.g. instance.method() in Swift and instance.method() in Rust. The latter is just syntactical sugar for T::method(&instance) or T::method(instance) where T is the type of the instance (depending on whether the item is being borrowed or moved). Given its implicit knowledge of/access to instance-local data, and the distinctive behavior of Swift methods (see below), I don’t think the same is, or even could be, true of Swift.

All of Swift’s other behaviors around functions—internal and external names, and all the distinctions that go with those—are equally applicable to methods. Similarly, with the sole change that the first parameter is always the instance being acted on, a Rust methods follow all the same rules as ordinary Rust functions (which is why you can call the struct or enum method with an instance parameter as in the example above).

Swift does have a self—it is, of course, implicit. It’s useful at times for disambiguation—basically, when a parameter name shadows an instance name. This will look familiar to people coming from Ruby.

The strong distinction Swift makes between reference and value types comes into play on methods, as you might expect, as does its approach to mutability. Methods which change the values in value types (struct or enum instances) have to be declared mutating func. This kind of explicit-ness is good. As we discussed in Part 10, Rust approaches this entire problem differently: types are not value or reference types; they are either mutable and passed mutably (including as mut self or &mut self), or they are not. If an instance is mutable and passed mutably, a method is free to act on instance data. And in fact both languages require that the instance in question not be immutable. In fact, everything we said in Part 10 about both languages applies here, just with the addendum that private properties are available to methods.

The distinction, you’ll note, is in where the indication that there’s a mutation happens. Swift has a special keyword combination (mutating func) for this. With Rust, it’s the same as every other function which mutates an argument. This makes Rust slightly more verbose, but it also means that in cases like this, the existing language tooling is perfectly capable of handling what has to be a special syntactical case in Swift.

Both Swift and Rust let you out-and-out change the instance by assigning to self, albeit in fairly different ways. In Swift, you’d write a mutating method which updates the instance proper like this:

struct Point {
    var x = 0.0, y = 0.0
    mutating func changeSelf(x: Double, y: Double) {
        self = Point(x: x, y: y)
    }
}

In Rust, you’d need to explicitly pass a mutable reference and dereference it. (If you tried to pass mut self instead of &mut self, it would fail unless you returned the newly created object and assigned it outside.) Note that while the full implementation here is a couple lines longer, because of the data-vs.-method separation discussed earlier, the implementation of the method itself is roughly the same length.

pub struct Point {
    pub x: f64,
    pub y: f64,
}

impl Point {
    pub fn change_self(&mut self, x: i32, y: i32) {
        *self = Point { x: x, y: y };
    }
}

Note that though you can do this, I’m not sure it’s particularly Rustic. My own instinct would be to get a new Point rather than mutate an existing one, in either language, and let the other be cleaned up “behind the scenes” as it were (with automatic memory management in Swift or the compiler’s automatic destruction of the type in Rust)—purer functions being my preference these days.

You can do this with enum types as well, which the Swift book illustrates with a three-state switch which updates the value type passed to a new value when calling its next() method. You can do the same in Rust, with the same reference/dereference approach as above.

Here’s a three-state switch in Swift:

enum ThreeState {
    case First, Second, Third
    mutating func next() {
        switch self {
        case First:
            self = Second
        case Second:
            self = Third
        case Third
            self = First
        }
    }
}

And the same in Rust:

enum ThreeState { First, Second, Third }
impl ThreeState {
    pub fn next(&mut self) {
        match *self {
            ThreeState::First => *self = ThreeState::Second,
            ThreeState::Second => *self = ThreeState::Third,
            ThreeState::Third => *self = ThreeState::First,
        }
    }
}

Both languages also have what Swift calls “type methods”, and which you might think of as “static class methods” coming from a language like Java or C♯. In Swift, you define them by adding the static or class keywords to the func definition. The class func keyword combo is only applicable in class bodies, and indicates that sub-classes may override the method definition.

struct Bar {
    static func quux() { print("Seriously, what's a `quux`?") }
}

func main() {
    Bar.quux()
}

In Rust, you simply drop self as a first parameter and call it with :: syntax instead of . syntax:

struct Bar;
impl Bar {
    pub fn quux() { println!("Seriously, what's a `quux`?"); }
}

fn main() {
    Bar::quux();
}

As usual, Rust chooses to use existing language machinery; Swift uses new (combinations of) keywords.