Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Manifesto: Adding memory ownership to Swift (swift.org)
118 points by mpweiher on Feb 17, 2017 | hide | past | favorite | 29 comments



Hit me up with any questions y'all have.


The Swift ownership system doesn't address concurrency because a mutable reference can be passed to a function, stored somewhere, and then passed to another function?


The major holes are: class fields, globals, and captures of escaping (non-scoped) closures (which are basically class fields).

Although this system proposes dynamic checking for these cases, it's explicitly not atomic checking. Atomic checking would be very expensive even though most code doesn't need it. Also there's a general sense that java, which does atomically guard all shared mutable state, has the problem that these accesses are defined but ultimately useless. Part of this is that coherent updates require coarser locks.


Maybe for objects it makes more sense to be able to "own" the object as a whole, if I am understanding you right. Still reading the Swift proposal.


I'm at work and went diving into the doc looking for one specific thing. I found it, but it turns out to be a put unfortunate.

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

> Swift is permitted to destroy a value (and thus call deinit) at any time between its last use and its formal point of destruction. The exact definition of "use" for the purposes of this definition is not yet fully decided.

For things like scoped lockables the last "use" must be well defined with respect to the termination of the scope. Early deinit means you're still unable to build something like C++'s std::lock_guard (http://en.cppreference.com/w/cpp/thread/lock_guard).

Consider:

    // dwizzles can be fizzled up to three times, but no more.
    class dwizzle {
     public:
      dwizzle() : count_(0) {}

      void fizzle() {
        std::lock_guard lock(&mutex_);
        if (count_ >= 3) return;
        ++count_;
      }  
     private:
      std::mutex mutex_;
      int count_ = 0;
    };
Obviously a silly toy, but the point is that we acquire the lock, we must hold it until the end of the scope for correctness, and we want to ensure it's dropped on early returns.

I hope any formal proposal coming out of this has a sufficiently strict definition of "use" (or provides opt-in tools for building structs that restrict the definition appropriately) to allow such constructions. It's much nicer than having to always type something like:

    mutex.lock()
    defer {
      mutex.unlock()
    }
(apologies for any inconsistency in my C++ above -- the codebase I routinely work in has its own mutex type and an equivalent scoped lockable -- I tried to adopt the more standard version for universality's sake).


The argument for Swift's behaviour is basically: very few things actually need this guarantee, and it's bad for performance (or memory usage) for all the other types to inherit it.

Personally, as someone who has spent a lot of time trying to manage the safe/unsafe code boundary, I'm inclined to agree with you. Unsafe code in Swift has an even scarrier subtle hazard: if the last uses of a value are through an unmanaged handle, the compiler won't know and can free the managed value before those uses. You need to insert a marker saying "please keep this alive". Even worse: returning a value, after inlining, isn't sufficient to establish a use!

That said, your lock handle example doesn't matter in swift today: you really want a lock handle to be a struct, and structs can't have destructors. You also want it to have move semantics, which don't exist yet. This proposal suggests moveonly values that could adopt destructors (like Rust), and if you're already adding an annotation for destructors, it's not out of the question to add another one asking for "true" lexical destructors.


Indeed, my point was that if you're going to add moveonly types with destructors having a way to force lexically scoped lifetime has concrete uses.

That said, having that be optional opt-in behavior could be valuable from a compiler optimization freedom perspective.


The correct way would be to use a trailing closure.

Something like

    func fizzle() {
        lock_guard(mutex) {
           if (count_ >= 3) {return; }
           count_ = count_ + 1;
        }
    }

Where lock_guard() might be then something like this, implemented on some utility library:

    func lock_guard (_ m:Mutex, action: () -> Void) -> Void {
        m.lock();
        defer {
          m.unlock();
        }
        action();
    }
This is one of the RAII approaches in functional programming, using partial function application and higher order functions, to create new types of operations.


This is fair, and what I've done so far in Swift (with a cute little extension on NSLocking no less :).

If you're going to add moveonly types with destructors, though, it seems strongly desirable to give them (or at least making available) lexical RAII semantics.


Obj-C ARC could do this with NS_VALID_UNTIL_END_OF_SCOPE, but Swift only has withExtendedLifetime(), which is probably more typing than just using defer.

(The ObjC version would break if you ever threw an exception, ARC doesn't release objects during unwinding!)


> withExtendedLifetime(), which is probably more typing than just using defer.

with..., arrow down, enter.


That code executes 4 times.

That's not how you do things in Swift:

  class Dwizzle {

  	private let countQueue = DispatchQueue(label: "hey debugger! I'm here!")
  	private var count = 0 //type inference to Int

  	func fizzle() {
  		countQueue.sync {
  			if count > 3 { return }
  			count += 1
  		}
  	}
  }
See, no need for mutexes, or defers, or that lock_guard.

Just simple closures you already know.

Now you want to expose the count variable to other types, always in a non-negotiable thread-safe manner?

  	private let countQueue = DispatchQueue(label: "hey debugger! I'm here!")
  	private var _count = 0
  	public var count : Int {
  		get {
  			return countQueue.sync {
  				return _count
  			}
  		}
  		set(newValue){
  			if newValue >= 3 {
  				self.countQueue.sync {
  					self.count = newValue
  				}
  			}
  		}
  	}
And maintain the rest.

Now what if you want to clean up, and need to only one fizzling executing at a time in the whole process? Doesn't matter which process calls the fizzling?

  class Dwizzle {

  	private static let countQueue = DispatchQueue(label: "hey debugger! I'm here!")
  	private var _count = 0
  	public var count : Int {
  		get {
  			return Dwizzle.countQueue.sync {
  				return _count
  			}
  		}
  		set(newValue){
  			if newValue >= 3 {
  				Dwizzle.countQueue.sync {
  					self.count = newValue
  				}
  			}
  		}
  	}

  	func fizzle() {
  		if count > 3 { return }
  		count += 1
  	}
  }
To me, your code looks like a semaphore, let me introduce you to my over-the-knee fully-functional semaphore that can implement your Dwizzle class:

  class HackerNewsSemaphore {
  
  	private let queue = DispatchQueue(label: "sem queue")
  	private var count = 0

  	func up(){
  		queue.sync {
  			count += 1
  			if executeWhenUp && onlyExecuteWhen(count) {
  				execute(count)
  			}
  		}
  	}

  	func down(){
  		queue.sync {
  			count -= 1
  			if count > 0 && executeWhenDown && onlyExecuteWhen(count) {
  				execute(count)
  			}
  		}
  	}

  	var executeWhenUp : Bool
  	var executeWhenDown : Bool

  	var onlyExecuteWhen : (Int)->Bool

  	var execute : (Int)->Void

  	init(whenTrue: @escaping (Int)->Bool, whenUp: Bool, whenDown: Bool, execute: @escaping (Int)->Void) {
  		self.onlyExecuteWhen = whenTrue
  		self.executeWhenUp = whenUp
  		self.executeWhenDown = whenDown
  		self.execute = execute
  	}
  }


  //test
  let dwizzle = HackerNewsSemaphore(whenTrue: { (i) -> Bool in return i <= 3},
                                    whenUp: true,
                                    whenDown: true,
                                    execute: { i in print("fizzle \(i)") })

  for x in 0...10 {
  	dwizzle.up()
  }

  for x in 0...10 {
  	dwizzle.down()
  }

  //output:
  fizzle 1
  fizzle 2
  fizzle 3
  fizzle 3
  fizzle 2
  fizzle 1


I appreciate the GCD approach, and for many problems it'd absolutely be what I'd suggest and use myself. My little Dwizzle toy could certainly be built that way. For concrete systems programming tasks that deal with things on a per-IOP basis, for example, it's specified semantics are insufficient make strong performance assertions.

It's not a blocking semaphore -- we explicitly don't want it to block after the third call. The example was an entirely artificial construction to get (a) an early return and (b) a use for locking for thread safety (you could of course also do it with just an opportunistic CAS loop). That said, yes you could build it with a semaphore that included tryDown() in its contract.

My day job is working on the VMM that backs Google Compute Engine. Thread hops (it's the wakeups, really) have a measurable and for some workloads substantial performance and efficiency penalty. This makes the use of GCD and similar technologies untenable (while GCD could be written such that dispatch queues handle synchronous dispatches on the calling thread, the docs do not guarantee those semantics iirc). To really drive the point home Hypervisor.framework requires that some calls be made from specific threads, so taking on a similar project to my day job in Swift would really absolutely require knowing details about execution context.

I've been building a little toy VMM, mostly as an opportunity to learn Swift outside of work, so my concerns aren't entirely hypothetical (for a null or nearly-null operation like fizzle GCD is up to a hundred times slower in Swift today than a spinlock lock/unlock with warm caches). This still puts dispatch in the realm of microseconds, though, so for many workloads it's fine. It's too long (and way too many cycles spent in actual compute) for things like dispatching network packets or flash storage IOs, though, particularly if overall cycle budget is a concern.


Swift doesn't have memory ownership because your supposed to use DispatchQueues for that.

The end.

People asking for this are the people not familiar with Apple's SDK. And btw, DispatchQueues are also available in Linux, GCD has been ported to Linux since the beginning, but neckbeards have been trying to avoid it at all costs because it's Apple, and now they are complaining because Swift has been taking the spotlight from Rust.


I think this argument is the real justification :

"Memory ownership model: an (opt-in) Cyclone/Rust-inspired memory ownership model is highly desired by systems programmers and for other high-performance applications that want predictable and deterministic performance"

I think it could be used to implement mechanism such as dispatch queues, which are for now implemented in C.


And what's the problem of being implemented in C?


What's your problem with it being implemented in Swift?

Because you phrased your answer in an adversary tone (what with neckbeards, people jealous of Swift "taking the spotlight" etc) as if there was some war going on.

Plus the whole, "It will always only be dispatch queues, deal with them, period" vibe I don't really care for.

I gather you are not an official spokesperson for the Swift team, and thus qualified to make such a call?

If you read TFA, it's an Apple engineer that says:

"Memory ownership is a topic that keeps poking its head up here. Core team members have mentioned several times that it's something we're interested in working on. "


[flagged]


> "see, APPLE COPIED this from the open source community, those bastards" like we constantly see from the Rust guys since Swift debuted

What? Not only did Lattner repeatedly state that Rust (among other languages) was an influence on Swift, the Rust developers are proud, not salty about that fact. In fact, they're quite open about taking features from Swift as well (e.g. `if let`). Three of Apple's Swift developers are former Rust developers/interns.


I know that, but there's a difference between those who do, and those who talk about it.

And all I see here is those who talk about it.


>And all I see here is those who talk about it.

That's because here is not the official mailing list of either Rust or Swift. DUH!

And even here, 99% of those who comment, don't talk about for the envy/war reasons that you attribute to them.

In fact you're one of the very few to bring up this BS rivalry thing.


swift ambitions to become a system programming language. That means to replace C.


Yes, let's rush the development of the language, just to say you can do X. /s


The Swift memory model is current incoherent; this is an attempt to fix that. (defining accesses properly, properly preventing conflicting accesses in single threaded ways)

Boring single-threaded swift code can currently be incredibly slow for no clear reason; this is an attempt to fix that.(addressors++)

The details this manifesto covers need to be settled for ABI-stability, which is still one of the primary goals for Swift. (because it enables making it a part of Apple's OSes)


No, it's not.


Many of us who like Rust also like Swift, and there are many Swift team members who have contributed to Rust.

What I do wonder about it is Swift's plan for things like tiny binaries (no runtime), no standard library, zero-cost (or predictable cost) abstractions...


Swift is an Applications programming language first, digging its way down to the system.

Rust on the other hand tries to be a system programming language, but with no success at that. And it's not a popular Applications programming language because there's no support from any major software platform vendor.

Swift doesn't have a runtime, a linked library isn't a runtime, if you think it is, then you don't have the qualifications to discuss this.

It is not a problem to store one 50meg file on my 32GB iPhone or 128GB server, it would be in a 32KB microcontroller, but nobody is trying to ship Swift as a language for those.


> Swift doesn't have a runtime, a linked library isn't a runtime, if you think it is, then you don't have the qualifications to discuss this.

My understanding is that it's called "the Objective-C runtime". I don't need any qualifications to call it that!

I'm not sure what distinction you're trying to draw.


Neckbeards?




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: