Hacker News new | ask | show | jobs
by leshow 3193 days ago
what does & do in Swift then? And what's the purpose of even having inout and & if you can't count on the code to actually be an address?
3 comments

& is just a sigil saying "I acknowledge that I am passing this as an inout parameter, and therefore the value may be modified by the function I am calling." Note that & is only legal on a function parameter. You cannot write, for example, `let b = &a`.

The purpose is to allow for out-parameters. A classic example would be the `+=` operator. (Swift operators are just normal functions with special call syntax.) It takes its first parameter as `inout` so that it can mutate the value.

Note that inout parameters work with expressions where it would be impossible to take the address. For example, you can use & on a computed property that has a setter. In that case it has to read the initial value, pass that to the function, then write back the new value, because it has no idea where the computed property actually stores the value, if anywhere.

Edit: because I'm obsessive and weird, I made a quick example of this computed property stuff:

http://swift.sandbox.bluemix.net/#/repl/59c284376cbea87f72c4...

Click the play triangle at the bottom to see the output.

> (Swift operators are just normal functions with special call syntax.)

Coming from Haskell and Rust it's nice to see this trend catching on.

Is Swift planning to introduce a distinction between borrows and mutable borrows to the user? From what you describe it seems like right now syntax-wise a borrow and a mutable borrow look the same, and the runtime makes some decision about it.

edit: Or I guess it could be the opposite. Since Swift passes by value always unless the runtime can optimize (right?), you could just not write & and inout and cross your fingers it gets optimized to a borrow rather than a copy?

Stuff like this makes me prefer the explicitness of Rust. It seems like here on the surface it's abstracted from you, but really you need to know the rules anyway or you could get into trouble.

> Coming from Haskell and Rust it's nice to see this trend catching on.

It was already like that in older languages like Lisp, Smalltalk and CLU, just C++ made them in a different way.

What's the use of a non-mutable borrow? Is it just for speed, to avoid copying a large structure? Large structures are rare in Swift and probably not important to optimize. (Value types like String and Array are actually just one reference under the hood, and that's all that gets "copied" when you pass those by value.)
It also lets one avoid reference-counting traffic, which can be significant for values that contain multiple ARC/COW things (such as strings and arrays), and is semantically critical with move-only (or "unique ownership", per the ownership manifesto[1]) types.

[1]: https://github.com/apple/swift/blob/master/docs/OwnershipMan...

Unique ownership would be really nifty, and it makes sense that you'd definitely need pass by reference for it. Thanks for explaining!
I don't know Swift, but non-mutable borrows are useful in a large number of languages. Are you sure a String is represented as a single reference? In Rust a String has a reference to the actual contents on the heap, a length, and a capacity. So that's a bit heavier than just a single reference, same thing with Vec. Rust's semantics are similar in that if you pass a Vec it will 'move' those 3 things, still, passing a string or vector slice (an immutable borrow) is faster, and moves less data, because it only copies a single reference.

I find it hard to believe that 'large structures are rare in Swift'. People don't make structs with multiple fields? You never want other structs to hold one or many of those? These are cases where non mutable borrows are useful. I understand that in Swift this is probably abstracted away from you and done by the runtime (or compiler) if it can, but that doesn't mean non-mutable borrows aren't useful; you just probably don't see them.

Of course, that's just a guess, I don't know Swift.

You're right, String has three fields. It's Array that's just a single reference. Is the overhead of passing three fields as a parameter so high that passing a reference is faster? Pointer chasing isn't free either, after all. I can certainly imagine scenarios where that sort of microoptimization pays off, but it seems like it would be rare.

As far as large structures go, I'm thinking "large" like hundreds of fields. Any time I've seen people concerned about large structures, they misunderstand the value-typedness of things like String and Array and are worried about the contents of those things, which isn't really part of the size of the struct itself. But that's just what I've seen.

From the documentation of Array for Swift it has the same behaviour as Rust's Vec (it's growable, it allocates double the capacity after reaching max length). I'd find it pretty odd if it didn't share the same 3 word length as String. Also, how would it quickly know it's length if it didn't also store it's length in the same structure as the ptr to it's heap location? You'd have to chase 2 pointers just to get the length.

I'd guess that Swift also has something analogous to an array slice, which would be a (possibly) immutable borrow to a chunk of array data on the heap. This also happens to be a good use case for borrows.

From the other comment here it seems like Swift is pursuing an ownership model similar to Rusts, in which case, immutable borrows will become more important when you think about struct contents. You can only have a single owner, but you can specify many borrowers. This kind of thing is important when you have an array or vector of types, often you don't want those types to have a single owner but you want them to be populated or store a reference from somewhere else.

Anyway, don't dismiss the concept out of hand. Immutable borrows definitely have their uses, whether it's made explicit to you or not in Swift is another thing entirely.

Still new to Swift but I believe & is an explicit syntax necessary to make clear in the code that the function being called is mutating the argument, and thus the variable passed into that function could be mutated. It must be used wherever an argument is in/out. It makes mutation explicit both in the function declaration and also in the function call. Which is nice!

It's good for code readability but also prevents accidentally passing a variable to a function that could mutate it when you weren't expecting that, and vice-versa.

Values are not guaranteed to even have an address, IIRC.