This version of the site is now archived. See the next version at v5.chriskrycho.com.

Rust and Swift (x)

Classes and structs (product types), and reference and value types.

December 06, 2015 (updated December 22, 2015)Filed under Tech#listicles#rust#rust-and-swift#software development#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.)


  • Swift and Rust both have “product types” as well as the enum “sum types.” In Rust, these are struct types; Swift splits them into classes and structs.

  • “Product types” will be much more familiar to programmers coming from a C-like background, or indeed most object-oriented programming languages: these are the same basic kind of thing as classes, structs, and objects in other languages. These include all the value types which compose them, unlike sum types—enum—which have only one of the value types which compose them.

  • Right off the bat, I note the Swift book’s somewhat amusing reticence to call out C and C-descended languages:

    Unlike other programming languages, Swift does not require you to create separate interface and implementation files for custom classes and structures.

    Because there’s such a long list of languages not directly descended from C which do that, right? 😉

  • Rust differs not only from Swift but from every other modern language I have used in not having a constructor syntax for its instantiations. Whereas C++ has new NameOfType() and Python and Swift both have NameOfType(), “constructors” for Rust structs are just functions which return an instance constructed using literal syntax, by convention NameOfType::new().

  • Let’s make a struct defining a location in a plane, you might do this in Swift (leaving aside initializer values; I’ll come back to those later). These definitions look very similar. Swift:

    struct Point {
        var x: Double var y: Double
    }

    Rust:

    struct Point {
        x: f64,
        y: f64,
    }
  • Creating the types looks a little different, though. Here’s a constructor in Swift:

    let point = Point(x: 0, y: 0)

    And the two ways we could construct the type in Rust, a literal constructor (fairly similar to constructing dict literals in Python or object literals in JavaScript):

    let point = Point { x: 0.0, y: 0.0 };

    Or a constructor method, new:

    // "Constructor"
    impl Point {
        fn new(x: f64, y: f64) -> Point {
            Point { x: x, y: y }
        }
    }
    
    let another_point = Point::new(0, 0);

    Observe: these two things in Rust are the same under the covers (though if Points had non-public internals, they would be non-trivially different: you couldn’t construct it with its private members externally). As usual, Rust opts to keep the language relatively small in these core areas. Given the plethora of ways you can construct something in e.g. C++, I count that a big win.

  • Another difference: Swift has syntax for default values; Rust uses a trait instead. In Swift, you simply supply the default value in the definition of the struct or class:

    struct Point {
        var x = 0.0 var y = 0.0
    }
    
    let point = Point()

    In Rust, you use std::default::Default, which provides a standard value for a given type, and for simple types can be supplied by the compiler even for custom types. Here is the equivalent Rust code:

    use std::default::Default;
    
    #[derive(Default)]
    struct Point {
        x: f64,
        y: f64,
    }
    
    let point = Point::default();

    This is reasonable enough, but we can also supply our own custom implementation if we so desire:

    use std::default::Default;
    
    struct Point {
        x: f64,
        y: f64,
    }
    
    impl Default for Point {
        fn default() -> Point {
            Point { x: 0.0, y: 0.0 }
        }
    }
    
    let point = Point::default();

    Of course, this is trivial for this type, but you can see how it could be useful for more complex types.

  • The tradeoffs here are our usual suspects: Rust’s re-use of an existing concept/tool within the language (trait) vs. Swift’s use of syntax. Rust is slightly more explicit, making it obvious that a default value is being created—but Swift is perfectly readable and the syntax is consistent with many other languages, and it is shorter.

  • Both languages use . syntax for member access. Swift:

    println("The point is: \(point.x), \(point.y)")

    Rust:

    println!("The point is {:}, {:}", point.x, point.y);
  • Swift lets you define items within a struct as mutable or constant. So you can create a variable struct instance, with some of its items immutable:

    struct PointOnZAxis {
        var x: Double var y: Double let z = 0.0
    }
    
    var point = PointOnZAxis(x: 4.0, 5.0)
    point.x = 5.0 point.y = 6.0
    // This wouldn't compile, though:
    // point.z = 1.0

    This is pretty handy for a lot of object-oriented programming approaches.

  • And Rust doesn’t have it. There are ways to accomplish the same thing; this isn’t the end of the world. Still, it’s an interesting omission, and it’s very much by design. Rust used to have this feature, and dropped it—and for good reason. Say you had a mutable field in a mutable struct, and then an immutable reference to it; should the mutable field be mutable, or immutable, with that reference?

  • The Rusty way to do this is to differentiate between public and private data. The above examples don’t make the public/private distinction particularly clear, because they assume everything is within the same module. However, many times, this will not be the case.

    mod geometry {
        pub struct Point {
            x: f64,
            pub y: f64,
        }
    
        impl Point {
            pub fn new() -> Point {
                Point { x: 0.0, y: 0.0 }
            }
    
            pub fn set_x(&mut self, x: f64) {
                self.x = x;
            }
        }
    }
    
    fn main() {
        // Won't compile: the `x` field is private.
        // let mut p = geometry::Point { x: 0.0, y: 0.0 };
    
        // Will compile: the `new` method is public.
        let mut p = geometry::Point::new();
    
        // Won't compile: `x` isn't public.
        // p.x = 4.0;
        // You can use the setter, though:
        p.set_x(4.0);
    
        // You *can* set `y` directly, though, because it's public.
        p.y = 14.0;
    
        // You can't set fields either way if the instance is immutable.
        let q = geometry::Point::new();
    
        // This fails because `set_x` requires a mutable reference, but `q` is
        // immutable.
        // q.set_x(4.0);
    
        // This fails because `q` is immutable, and so all its fields are, too.
        // q.y = 14.0;
    }
  • This is an interesting way of handling this issue. Rust takes the fairly standard use of information hiding (one of the basic principles of most object-oriented programming techniques) and combines it with the language’s normal mutability rules to make it so that the mutability of any given instance data is quite clear: all public members are just as mutable as the struct. If a member isn’t potentially publicly mutable, it isn’t publicly accessible. I really like this, though it took some mental readjustment.

  • There’s one other difference here, and it’s actually one of the areas Swift and Rust diverge substantially. Rust has struct for all product types; Swift splits them into struct types and class types.

  • Swift classes have inheritance; there is presently no inheritance in Rust.

  • Additionally, whereas Rust determines whether to use pass-by-reference or-value depending on details of the type (whether it implements the Copy trait) and expected arguments to a function, Swift makes that distinction between class (pass-by-reference) and struct (pass-by-value) types. Quirky.

  • Not bad, per se. But quirky.

    Edit: I recently bumped into some discussion of data types in C♯ along with C, C++, and Java (here) and discovered that Swift is stealing this idea from C♯, which makes the same copy/reference distinction between struct and class.

  • One consequence of this: in Rust, you’re always rather explicit about whether you’re accessing things by value vs. by reference. Not so in Swift; you have to remember whether the item you’re touching is a struct type or a class type, so that you can remember whether a given assignment or function call results in a reference or a copy. This is necessary because Swift doesn’t let you make that explicit (trying to hide the memory management from you). And it’s not alone in that, of course; many other high-level languages obscure that for convenience but still require you to think about it in certain circumstances. I’ve been bitten in the past by the value/reference distinction when thinking through the behavior of Python objects, for example, so that’s not a critique of Swift. Moreover, having the distinction between struct and class types does let you be more explicit than you might in e.g. Python about how given data will be handled.

  • I won’t lie, though: I like Rust’s approach better. (Shocking, I know.)

  • All that nice initializer syntax for Swift struct types is absent for its class types, which seems strange to me.

  • Swift supplies some syntax for object identity, since it’s useful to know not only whether two class instances have the same data, but are in fact the same instance. You can use === and !==. Handy enough. To get at this kind of equivalence in Rust, you have to use raw pointers (which are often but not always unsafe; you can do this specific comparison without being unsafe, for example) to check whether the memory addresses are the same.