telotortium 9 years ago
  • Gankro 9 years ago

    Hit me up with any questions y'all have.

    • solidsnack9000 9 years ago

      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?

      • Gankro 9 years ago

        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.

        • solidsnack9000 9 years ago

          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.

jsolson 9 years ago

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).

  • Gankro 9 years ago

    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.

    • jsolson 9 years ago

      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.

  • astrange 9 years ago

    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!)

    • pjmlp 9 years ago

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

      with..., arrow down, enter.

  • pjmlp 9 years ago

    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.

    • jsolson 9 years ago

      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.

  • Tanegashima 9 years ago

    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
    • jsolson 9 years ago

      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.

Tanegashima 9 years ago

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.

  • solidsnack9000 9 years ago

    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...

    • Tanegashima 9 years ago

      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.

      • solidsnack9000 9 years ago

        > 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.

  • bsaul 9 years ago

    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.

    • Tanegashima 9 years ago

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

      • bsaul 9 years ago

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

        • Tanegashima 9 years ago

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

      • coldtea 9 years ago

        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. "

  • Gankro 9 years ago

    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)