yield is actually already implemented in the language for use with _read and _modify.
There is a fundamental difference between return and yield: return produces a value, while yield lends a value. This means that, unlike code after a return statement, code after a yield statement will be run (unless an error is thrown). And, if generators are implemented in Swift, yield could be called multiple times.
While we could reuse the return keyword, I think yield is a different-enough concept to warrant a new keyword, especially since new keywords in Swift donât break code the way that new keywords in other languages do.
_modify {
_makeMutableAndUnique() // makes the array native, too
_checkSubscript_mutating(index)
let address = _buffer.mutableFirstElementAddress + index
yield &address.pointee
_endMutation();
}
(I donât have particularly strong feelings about having an implicit yield for single-expression accessors â I can see good arguments both ways.)
_endMutation() is called after the value is mutated.
...
anArray[0] = 0
// _endMutation is implicitly called here
...
I donât know â I canât see any reason why it would be necessary. My guess is that a programmer got confused with another language and put it in without thinking.
@dabrahams before we spill too much ink here, why don't we wait for @Andrew_Trick to post the document that he promised which describes in more detail what we mean by lexical lifetimes.
It might make more sense if you think about it like a function that accepts a closure:
extension Array {
mutating func withElement(_ index: Int, _ body: (inout Element) throws -> Void) rethrows {
_makeMutableAndUnique() // makes the array native, too
_checkSubscript_mutating(index)
let address = _buffer.mutableFirstElementAddress + index
try body(&address.pointee)
_endMutation();
}
}
anArray.withElement(0) { $0 = 0 }
I do agree that itâs a bit different from yield in other programming languages, but it is similar in that it brings the surrounding code to a halt that can be resumed later.
It would be great to drop support COW types with ARC and replace them with structs with deinit, copy and move constructors. And add SharedPointer, MutableSharedPointer(with sync), UniquePointer for concurrency.
I appreciate what this proposal is doing, but I worry that complexity will inevitably creep into code that doesn't need it.
My understanding is that under this proposal, most code shouldn't need to change at all â only areas that are highly sensitive to performance should need to worry about the new semantics and features. But I don't know how users should know where to draw the line. It seems likely that organizations will adopt move() to assign instance variables from init() arguments as a standard practice because it can avoid a performance pitfall, and it's hard to make a compelling argument against doing it all the time.
I like how in Objective-C you can disable ARC on a per-file basis. This gives programmers the control they need for critical bits of code while being sufficiently onerous that no one would do it unless necessary. I don't think it's possible or desirable to take a similar approach with Swift, but I hope that we land on something that would be less tempting to use when it's not truly needed.
I would like to also highlight the inconsistency in capitalization and wording. Why is it @Sendable, but @noImplicitCopy, and @nonescaping instead of @nonEscaping, @noEscape or @NonEscapable?
(I understand this is more of a bike-shedding issue, and perfectly happy if the discussion of these inconsistencies is moved to a separate thread. Just wasn't sure if these were addressed at any point).
@noImplicitCopy is the latest in a long line of function attributes that will probably get replaced by a more comprehensive system someday. Compare with @inlinable, @Sendable, or @discardableResult.
consuming and nonconsuming are parameter keywords. Compare with inout.
@Sendable is a bizarre choice in my eyes, brought about because it is named after the equivalent protocol (which functions canât conform to).
I emphatically disagree. Swift code should be safe, idiomatic and easily understood, and always performant. To the greatest extent possible, there should never be any perception of a choice between those three objectives.
If you want to avoid making a mistake, @noImplicitCopy sounds more than capable of ensuring minimal runtime overhead (assuming no one does something ridiculous like defensive copying while using it).
The more guarantees the compiler can be given about your code, the more aggressively it can optimize. Performance-tuning means identifying and eliminating unnecessary assumptions, which is only made harder by lower-level code.
I donât necessarily see it playing out like this.
I remember some pretty wild and dire speculation about how @dynamicMemberLookup was going to wreck Swiftâs whole aesthetic and turn it into JS/Python/RubyâŚyet since adoption, the general impact of that feature in practice has been close to zero.
Established language culture subsumes new language features 99% of the time.
I would be very interested to see the results of a toolchain built with an early version of this applied to existing code.
@dynamicMemberLookup ultimately went quite well, in my estimation. The danger isnât having language features, itâs having language features that are easily misunderstood or misused. If there is a perception (even an inaccurate one!) that Swift code is less performant if it doesnât use the features in this roadmap, those features will be misused.
Yeah, there was that one time when the community (including myself) worried too much about the impact of a new language feature. That doesn't mean we should never worry about anything ever again.
The features are very different from @dynamicMemerLookup. That feature is about API design, and there turned out not to be that many APIs which could benefit from it. These features are about performance, and impact basically every project.
Also - who doesn't care about performance? That's time you could be using to perform useful work, and energy that will drain the user's battery. I'll note that we're not even talking about getting better performance here; only predictable performance. Who wants unpredictable performance?
The thing I worry about is that people will see these annotations all over the place in highly tuned packages like swift-algorithms, -collections, NIO, etc, and think that this is what the "pros" do; that it's some secret trick for high performance and they should throw it around everywhere, too.
But it all depends on how we expose things. If we can clearly communicate that, for instance, move only exists to assert that a variable is dead at a certain point, or that a nonconsuming argument is a sort of hint that the value probably won't extend its lifetime beyond the function call, I think we can significantly limit potential misuse and (most importantly) make it so that code which uses these features is still comprehensible to newcomers.