5 ms·
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/b
by jsolson 10y 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/OwnershipManifesto.md#deinit-for-non-copyable-types 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 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 10y agoThe 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 10y agoIndeed, 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 10y agoObj-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 10y ago> withExtendedLifetime(), which is probably more typing than just using defer. with..., arrow down, enter.
- pjmlp 10y agoThe 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 10y agoThis 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.
- deleted 10y ago[deleted]
- deleted 10y ago[deleted]
- deleted 10y ago[deleted]
- deleted 10y ago[deleted]
- Tanegashima 10y agoThat 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