--- Title: Rust and Swift (xvi) Subtitle: "Initialization: another area where Swift has a lot more going on than Rust." Category: Tech Tags: [rust, swift, rust-and-swift, programming languages] Date: 2016-06-07 23:30 Series: Title: Rust and Swift Part: 16 modified: 2019-06-22 10:55 --- 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.)][series] [series]: http://v4.chriskrycho.com/rust-and-swift.html Thanks to ubsan, aatch, and niconii on the [#rust-lang IRC] for a fascinating discussion of the current status of Rust's initialization analysis, as well as some very interesting comments on what might be possible to do in the future. Everything actually interesting about Rust in this post comes from the conversation I had with them on the evening of March 13. [#rust-lang IRC]: https://client00.chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust --- The rules various languages have around construction and destruction of objects are *extremely* important for programmer safety and ergonomics. I think it's fair to say that both Swift and rust are actively trying to avoid some of the mistakes made in e.g. C++ which poorly affect both its safety and its ease of use for developers, albeit it in some superficially different ways. Both languages also support defining how types are destroyed, which we'll come back to in a future discussion. The basic aim both Rust and Swift have in this area seems to be the same: avoid *partially* initialized objects. (You don't want partially initialized objects. Ask Objective C developers.) Swift does this via its rules around *initializers*. Rust does it by requiring that all the values of a type be initialized at its creation. So, for example, the following *looks* like it should work, but it doesn't. You can initialize the variable piecemeal, but you cannot *use* it: ```rust #[derive(Debug)] // to make it printable. struct Foo { pub a: i32, pub b: f64, } fn main() { // This will compile, but `foo` will be useless. let mut foo: Foo; foo.a = 14; foo.b = 42.0; // This would actually fail to compile. Surprising? A bit! // println!("{:?}", foo); // This will work, though, because it fully constructs the type. let foo2 = Foo { a: 14, b: 42.0 }; println!("{:?}", foo); } ``` (The reasons why this is so are fairly complicated. See the addendum at the end for a brief discussion.) In any case, this means that especially with more complex data types, providing standard constructor-style methods like `new` or `default` is conventional and helpful. (If the type has non-public members, it's also strictly necessary.) Swift has a number of options for initializers, which correspond to things you in most cases can do in Rust, but in a very different way. First, Swift allows you to overload the `init` method on a type, so that you can have different constructors for different starting conditions. (This is, to my recollection, the first time any kind of overloading has come up so far in the Swift book---but that could just be my memory failing me. Certainly I haven't referenced it in any previous discussion, though.) The example offered by the Swift book is illuminating for the different approaches the languages take, so we'll run with it. Here's a class defining a Celsius type in Swift: ```swift struct Celsius { let temp: Double init(fromFahrenheit f: Double) { temp = 1.8 * (f - 32.0) } init(fromKelvin k: Double) { temp = k - 273.15 } } // Create an instance each way let freezing = Celsius(temp: 0) let balmy = Celsius(fromFahrenheit: 75.0) let absoluteZero = Celsius(fromKelvin: 0.0) ``` Note the internal and external parameter names. This is a common idiom Swift keeps (albeit with some non-trivial modification, and with [more to come]). More on this below; first, the same basic functionality in Rust: [more to come]: {>> TODO: Swift 3 naming changes <<} ```rust struct Celsius { temp: f64 } impl Celsius { fn from_fahrenheit(f: f64) -> Celsius { Celsius { temp: 1.8 * (f - 32.0) } } fn from_kelvin(k: f64) -> Celsius { Celsius { temp: k - 273.15 } } } // Create an instance each way let freezing = Celsius { temp: 0 }; let balmy = Celsius::from_fahrenheit(75.0); let absoluteZero = Celsius::from_kelvin(0.0); ``` (Note that there might be other considerations in implementing such types, like using a `Temperature` base `trait` or `protocol`, or employing type aliases, but those are for later entries!) You can see a point I made about Swift's initializer syntax back in [part x][10]: the way Rust reuses normal struct methods while Swift has the special initializers. Neither is clearly the "winner" here. Rust gets to use existing language machinery, simplifying our mental model a bit by not adding more syntax. On the other hand, the addition of initializer syntax lets Swift use a fairly familiar type construction syntax even for special initializer cases, and a leaves us with a bit less noise in the constructor method. Note, though, that initializers in Swift *are* special syntax; they're not just a special kind of method (as the absence of the `func` keyword emphasizes)---unlike Rust, where initializers really are just normal struct or instance methods. The Swift book notes this distinction: > In its simplest form, an initializer is like an instance method with no parameters, written using the `init` keyword. The new keyword is the thing I could do without. Perhaps it's just years of writing Python, but I really prefer it when constructors for types are just sugar and you can therefore reimplement them yourself, provide custom variations, etc. as it suits you. Introducing syntax instead of just picking a standard function to call at object instantiation means you lose that. At the same time, and in Swift's defense, I've only rarely wanted or needed to use those facilities in work in Python. It's a pragmatic decision---and it makes sense as such; it's just not where my preference lies. The cost is a bit higher than I'd prefer relative to the gain in convenience. Back to the initializers and the issue of overloading: the external parameter names (the *first* parameter) is one of the main ways Swift tells apart the initializers. This is necessitated, of course, by the choice of a keyword for the initializer; Rust doesn't have any *need* for this, and since Rust doesn't have overloading, it also *can't* do this. In Rust, different constructors/initializers will have different names, because they will simply be different methods. [**Edit:** I'm leaving this here for posterity, but it's incomplete. See below.] One other important thing falls out of this: the external parameter names are *required* when initializing a type in Swift. Because those parameter names are used to tell apart the constructor, this is not just necessary for the compiler. It's also an essential element of making the item readable for humans. Imagine if this were *not* the case---look again at the `Celsius` example: ```swift struct Celsius { let temp: Double init(fromFahrenheit f: Double) { temp = 1.8 * (f - 32.0) } init(fromKelvin k: Double) { temp = k - 273.15 } } // Create an instance each way let freezing = Celsius(0) let balmy = Celsius(75.0) // our old fromFahrenheit example let absoluteZero = Celsius(0.0) // our old "fromKelvin example ``` We as humans would have no idea what the constructors are supposed to do, and really at this point there would *necessarily* just be one constructor unless the later options took elements of another *type*. That would be fairly similar to how overloading works in C++, Java, or C^♯^, and while method overloading in those langauges is very *powerful*, it can also make it incredibly difficult to figure out exactly what method is being called. That includes when the constructor is being called. Take a look at the *long* list of [C^♯^ `DateTime` constructors][MSDN], for example: you have to either have this memorized, have the documentation open, or be able simply to infer from context what is going on. [MSDN]: https://msdn.microsoft.com/en-us/library/system.datetime(v=vs.110) *Given* the choice of a keyword to mark initializers, then, Swift's rule about external parameter name usage wherever there is more than one initializer is quite sensible. [**Edit:** several readers, most notably including [Joe Groff], who works on Swift for Apple, pointed out that Swift *does* support overloading, including in `init()` calls, and uses types to distinguish them. Moreover, you can leave off the label for the parameter. My initial summary was simply incorrect. I think this is a function of my not having finished the chapter yet.] [Joe Groff]: https://twitter.com/jckarter/status/740763363626586112 Second, both languages support supplying default values for a constructed type. Swift does this via default values defined at the site of the property definition itself, or simply set directly from within an initializer: ```swift struct Kelvin { var temp: Double = 0.0 // zero kinetic energy!!! init () { temp = 305.0 // Change of plans: maybe just freezing is better } } ``` In Rust, you can not supply default values directly on a property, but you can define any number of custom constructors: ```rust struct Kelvin { temp: f64, } impl Kelvin { fn abs_zero() -> Kelvin { Kelvin { temp: 0.0 } } fn freezing() -> Kelvin { Kelvin { temp: 305.0 } } } ``` We could of course shorten each of those two one line, so: ```rust fn abs_zero() -> Kelvin { Kelvin { temp: 0.0 } } ``` The Rust is definitely a little noisier, and that is the downside of this tack. The upside is that these are just functions like any other. This is, in short, *exactly* the usual trade off we see in the languages. Rust also has the `Default` trait and the `#[derive(default)]` attribute for getting some basic defaults for a given value. You can either define a `Default` implementation yourself, or let Rust automatically do so if the underlying types have `Default` implemented: ```rust struct Kelvin { temp: f64, } // Do it ourselves impl Default for Kelvin { fn default() -> Kelvin { Kelvin { temp: 305.0 } } } // Let Rust do it for us: calling `Celsius::default()` will get us a default // temp of 0.0, since that's what `f64::default()` returns. #[derive(default)] struct Celsius { temp: f64, } ``` This doesn't get you quite the same thing as Swift's initializer values. It requires you to be slightly more explicit, but the tradeoff is that you also get a bit more control and flexibility. There's actually a lot more to say about initializers---there are *many* more pages in the Swift book about them---but this is already about 1,700 words long, and I've been slowly chipping away at it since March (!), so I'm going to split this chapter of the Swift book into multiple posts. More to come shortly! --- - [**Previous:** Inheritance: a Swiftian specialty (for now).][15] - [**Next:** More on initializers!][17] [10]: http://v4.chriskrycho.com/2016/rust-and-swift-x.html [15]: http://v4.chriskrycho.com/2016/rust-and-swift-xv.html [17]: /2016/rust-and-swift-xvii.html --- ## Addendum: No Late Initialization in Rust Returning to the first Rust example--- ```rust #[derive(Debug)] // to make it printable. struct Foo { pub a: i32, pub b: f64, } fn main() { // This will compmile, but `foo` will be useless. let mut foo: Foo; foo.a = 14; foo.b = 42.0; // This would actually fail to compile. Surprising? A bit! // println!("{:?}", foo); } ``` You can't do anything with that data for a few reasons (most of this discussion coming from ubsan, aatch, and niconii on the [#rust-lang IRC] back in March): 1. Rust lets you "move" data out of a struct on a per-field basis. (Rust's concept of "ownership" and "borrowing" is something we haven't discussed a lot so far in this series; my [podcast episode] about it is probably a good starting point.) The main takeaway here is that you could return `foo.a` distinctly from returning `foo`, and doing so would hand that data over while running the `foo` destructor mechanism. Likewise, you could pass `foo.b` to the function created by the `println!` macro 2. Rust allows you to re-initialize moved variables. I haven't dug enough to have an idea of what that would look like in practice. 3. Rust treats uninitialized variables the same as moved-from variables. This seems to be closely related to reason #2. The same "I'm not sure how to elaborate" qualification applies here. I'll see if I can add some further comments on (2) and (3) as I hit the later points in the Swift initialization chapter. [podcast episode]: http://www.newrustacean.com/show_notes/e002/index.html "New Rustacean e002: Something borrowed, something... moved?"