11 ms·
I put Swift into the category of "almost memory managed" languages and it's never fully clicked in my brain. Thinking about strong/weak/unowned references is mo
by kris-s 8y ago
I put Swift into the category of "almost memory managed" languages and it's never fully clicked in my brain. Thinking about strong/weak/unowned references is more difficult in my opinion than when to malloc/free something. Following the examples in the article just reinforced that maybe Swift's approach is a little too complex.
- giornogiovanna 8y agoRight. This is one of the things I don't like about automatic reference counting, that it's almost a complete solution, but cycles require more thought (but less cases) to deal with than manual memory management itself.
- mamcx 8y agoBut how many times the problem truly happened? Is in my experience so rare that the experience is alike with a GC.
- giornogiovanna 8y ago- Doubly linked lists. - Cocoa delegates that keep references to some parent of the objects that they're delegates of. - Some event listeners (in the same way as delegates). I wouldn't say it's "rare". It popped up pretty often for me when I was writing Objective C.
- azinman2 8y agoWhat’s not solved with weak refs?
- giornogiovanna 8y agoIt can always be solved with weak references.
- fauigerzigerk 8y agoOnly if you know that you haven't solved it already: https://news.ycombinator.com/item?id=19093677 https://news.ycombinator.com/item?id=19093677
- ben-schaaf 8y agoWhat's not solved with manual memory management? The point is not that you can't solve it, the point is that reference counting isn't the whole solution for garbage collection unlike a tracing gc for instance.
- saagarjha 8y agoWeak reference counting is still reference counting.
- int_19h 8y agoIt depends on your domain. Any data structure that has links in both directions becomes a pain, and it's pretty common to have trees like that. It's usually solved by having parents own the children but not vice versa, but then you run into cases where you want the whole thing to live so long as someone holds an owning reference to a child.
- mamcx 8y agoAgree. Is similar on rust, where circular links are also hard to do. Easy graphs is a killer feature of a GC. But also, is a localized problem. I have more issues with obj-c in the past, some ergonomic of swift make it even rarer. However, certainly suck for the ones that hit this!. I'm building a relational lang in rust and damm, I miss a GC!
- zozbot123 8y agoMany people in the Rust community use some sort of ECS in cases where something like a GC is needed. The whole point of ECS is that they work a lot like an ordinary GC runtime or an in-memory database, the underlying representation ensures that the system knows about all the "entities" that it needs to, and can reliably trace linkages between them. It might be easier to just use Go in cases where tracing GC is needed, though.
- Buttons840 8y agoI've only heard of using ECS for games. I'll have to look into them more closely.
- azinman2 8y agoWhen would you want the graph to exist due to some leaf more being retained but you wouldn’t know about it enough to maintain a ref to the root mode?
- int_19h 8y agoIn a language with tracing GC, you'd use a strong reference in both directions, so anybody who holds the child can always walk their way to the root (if you expose those references). Without such GC, you basically have to pass the root around, because it's what's holding all those children alive.
- pjmlp 8y agoAlike a tracing GC..,.
- dfgdghdf 8y agoIt would be interesting to compare the cognitive load of the Swift way and the Rust way. Rust gives you better guarantees, but you must annotate your code.
- giornogiovanna 8y agoRust doesn't just give you better guarantees. It gives you no runtime checking, and potentially even better performance than naive C, because it can automatically tell LLVM when there is no memory aliasing going on.[0] Also, it gives you more confidence, because you can, for example, give out an array to someone and be sure that it won't be modified. And usually that kind of confidence is enough to write safe, race-free parallel code. I don't think Swift's runtime exclusivity checks give you any of that. [0]: https://github.com/rust-lang/rust/pull/50744 https://github.com/rust-lang/rust/pull/50744
- glhaynes 8y agoSwift arrays have pass-by-value semantics (under the cover, they use copy-on-write to keep performance good) and are thus safe to pass to functions without concern that they'll be modified. (Unless it's passed as an `inout` parameter, which requires a prepended "&" at the call site.)
- zozbot123 8y agoRust can use its own bovine superpowers here https://doc.rust-lang.org/std/borrow/enum.Cow.html https://doc.rust-lang.org/std/borrow/enum.Cow.html to enable developers to write code that's invariant over whether it's dealing with a fresh copy of something, or just a borrowed reference. So, it's not quite correct to say that Swift's use of copy-on-write gives it better performance. Rust can express the exact same pattern quite easily, and do it with far more generality.
- glhaynes 8y agoI don't mean to imply that Swift's array performance is better than anything other than what its own performance would be if it didn't implement COW. (My intention was to convey that the normal way of passing arrays comes with confidence that your copy won't be modified by the callee.) Rust sounds nice, I'll have to try it sometime.
- favorited 8y agoYou can add a cycle collector on top of a reference-counted language. It's been discussed for Swift, but there are downsides which might make it not worth adding.
- saagarjha 8y agoThe benefit being that you only have to deal with this issue rarely, rather than all the time with manual memory management.
- fauigerzigerk 8y agoI find it extremely dangerous if issues arise rarely but not rarely enough stop thinking about them and not as rarely as you would think. Look at this terrifying example from [1]. How long do you have to look at this code to tell if there is a reference cycle or not? class ServiceLayer { // ... private var task: URLSessionDataTask? func foo(url: URL) { task = URLSession.shared.dataTask(with: url) { data, response, error in let result = // process data DispatchQueue.main.async { [weak self] in self?.handleResult(result) } } task?.resume() } deinit { task?.cancel() } } [1] http://marksands.github.io/2018/05/15/an-exhaustive-look-at-memory-management-in-swift.html http://marksands.github.io/2018/05/15/an-exhaustive-look-at-...
- saagarjha 8y agoI think the "catch" is that dataTask will retain self? Yeah, I agree that capture semantics with closures is a bit complicated to get right.
- fauigerzigerk 8y agoYes, dataTask will retain self in spite of the fact that [weak self] is used in the only place where self is explicitly referenced. This is baffling. After reading the linked article I have come to think that reference counting is not a good fit for a language that mixes object orientation with functional idioms including tons of closures.
- bsaul 8y agoThe examples are complex to follow because Swift model works just fine for all the normal cases. It only becomes problematic in some edge cases that regular programmer rarely meet. And with all the help that the compiler now provides (even before swift 5), it's becoming really really hard to shoot yourself in the foot.
- ken 8y agoI must be specially talented in this area, because I still run into memory issues fairly often. Just a couple days ago, I had a weak reference which was turning nil (Swift weak references are zeroing), and I couldn't figure out why. Both objects were still live. Property observers don't report the nil change, apparently. I couldn't figure out how to get the debugger or Instruments to provide any help, either. Eventually I just gave up and added an extra 'parent:' argument to an interface, for the one place it was actually needed. It's a bit more awkward than just keeping parent references in the tree nodes, but not too bad. For situations with multiple queues or threads, the situation is even worse. I wrap a lot of my GCD code in extra locks, which by my reading of the documentation shouldn't be necessary. Without them, it occasionally crashes with strange memory errors that are impossible to figure out. I felt like I wasted a few hours trying to track down my issue, the other day, and then come up with an alternative solution, while in any other modern language I could just have used a normal reference and counted on the GC to clean up the cycles when I'm done. Backwards compatibility with Objective-C obviously has tremendous value to Apple, and ARC is smaller and faster than tracing GC, but I feel like I'm paying for it over and over. In most HLLs, once I get past the low-level parts, I'm using a language specifically designed for my task, and I never have to touch the low-level parts again. Swift feels more like fancy C, in that I don't think I'll ever be able to stop thinking about subtle memory management issues.
- xenadu02 8y ago> I had a weak reference which was turning nil (Swift weak references are zeroing), and I couldn't figure out why This is extremely unlikely. If zeroing weak references were broken a lot of macOS/iOS would be broken. You probably have a bug or the memory just hasn't been overwritten yet, but the retain count is actually zero, you overwrote the reference, or something similar. Turn on Zombie Objects in Xcode then try it again. Objects are never deallocated but will turn into an instance of NSZombie when their refcount reaches zero. See if your supposedly "still alive" object is actually an NSZombie at that point. > I wrap a lot of my GCD code in extra locks ... Without them, it occasionally crashes with strange memory errors that are impossible to figure out You definitely have some race conditions or other concurrency bugs then. Common issues include being on a different queue than you expected (use dispatchPrecondition() to verify), being on a concurrent queue when you expected a serial queue, failure to use a barrier block on a concurrent queue (another good case where dispatchPrecondition() can help you), or accessing something both on and off the queue.
- diegoperini 8y agoLooking from a distance, what Swift does is actually taking best practices from modern C++ (14+) and incorporating them on the language level instead of introducing new library tools. If you regularly code C++, the "fuck, yes!" moment is there when you learn Swift.
- zozbot123 8y agoYou're actually describing what Rust is doing, more than anything. Swift has a lot more in common with Objective-C than C++, even though it does move away from the most challenging parts of Objective-C itself.
- gurkendoktor 8y agoThe flip side is that Swift has copied some bad parts of C++ as well: ridiculous compile times, value-type performance traps, ever-increasing complexity, mediocre IDE support, no ABI (I know, coming soon). Either way, to me Swift feels much closer to C++ than to Objective-C in spirit. I don't think it's a coincidence that there are at least two C++ committee veterans among its contributors :)