Here's a neat trick with `async let` I didn't realize. You can use it to avoid blocking on synchronous work.
@mattiem please let me unsee it 😄
@a_grebenyuk It is really verbose!
@mattiem what can I say, there is no lack of ways to move work to the background. But my favorite continues to be performSelectorInBackground:withObject:, of course.
@a_grebenyuk I’m partial to Thread.detached

@mattiem but, to be serious for a second, having a convenient way of turning any sync method into an async one would be cool, which this is not (convenient).

How about this lol?
(wait) no it doesn't work simply like that

@mattiem I'm not posting to the forum thread more, but I think it's a good demonstration that maybe, just maybe, moving work to the background needs to be a bit more verbose for the sake of clarify.

"clarity at the point of use is your most important goal." (which is long forgotten)

https://www.swift.org/documentation/api-design-guidelines/#fundamentals

Swift.org

Swift is a general-purpose programming language built using a modern approach to safety, performance, and software design patterns.

Swift.org

@a_grebenyuk yeah.

I haven't thought really deeply about this. But I'm pretty sure callee-defined correctness/safety is very good, but caller-defined background work is what's missing.

(and yes, it needs to be clear, even if the compiler can prove it to be safe)

@mattiem oops, it doesn't compile. This does:

@a_grebenyuk Right. I think a little sugar could make this quite nice.

However I found a bug that makes this harder to use, and seems to apply to closure literals too

https://github.com/swiftlang/swift/issues/76727

async let + sending doesn't seem to work · Issue #76727 · swiftlang/swift

Description I was trying to get around returning a non-Sendable type from a synchronous function. I thought I'd be able to use sending to deal with this, but it doesn't seem to work. Reproduction c...

GitHub
@mattiem I would prefer to use none of these options. If you look at this with no Swift Concurrency baggage, you would expect it to run synchronously and return a value immediately since there are no suspension points.
@mattiem take this with a grain of salt as I'm no expert, but in other languages I encountered, such as C# and JavaScript, async methods with no suspension points run synchronously or until the first suspension point.
@a_grebenyuk Yes and there’s a possibility that’s how it ends up working eventually in this case. I’m not 100% sure yet…
@mattiem @a_grebenyuk and that’s what I don’t like 😂 there is an await, you shouldn’t have to assume that it will be immediate and sync. Imo that’s a core principle we shouldn’t break.
@alexito4 @mattiem it is OK to assume that it will be async and dispatched on a global task executor suitable for running background work? It works both ways.
@a_grebenyuk @mattiem I partially agree, but in my view, it is not the same at all—by far. There is an await; it’s there for a reason.
@alexito4 @mattiem that's a fair point. I wonder if there is some middle ground. The main challenges you run into are with how you model isolation which this contrived example doesn't demonstrate. It's a tough call. I think it's unlikely it'll be possible to change at this point because it will be a 180 and not just on this part.
@a_grebenyuk @mattiem Yeah, there are a few axis to take into account that are hard to balance and figure out. I wish we had had more time to polish these things. I’m just hesitant to lose what makes Swift concurrency such a step forward compared to the rest. It’s already hard enough to help people not reach for unstructured concurrency just because it looks like what we had before. 😂

@alexito4 @a_grebenyuk I think you two are really on to something with this tension.

Today, “await” does not mean “won’t block”. It does not even mean “will be async”!

I really believe that the language is 90% of the way there. Providing a clearer mental model for non-experts PLUS some nice way for callers to be in control of blocking work are missing pieces.

@mattiem @alexito4 @a_grebenyuk that is an interesting point. Seems like an await should NOT be required in this case kind of like Actors calling into themselves.
@jasongregori @alexito4 @a_grebenyuk It is not for synchronous methods. But if async and isolation does not change, there is guaranteed to be no suspension on function call and return, which can be important.
@mattiem Do you (currently) get the same behaviour by pushing the decode behind an async function?
Cause async is implicitly nonisolated for the time being?

@rhysmorgan yes you do!

And there is a very significant advantage to doing it that way. Because the function signature is under your control, you can do use a `sending` return and avoid needing Sendable types.

@mattiem when you interiorize async lets are just sugar for task groups you start seeing things differently ^^ the struggle I have is to explain people every line of code still runs on sequence, every await will wait. what often happens is people then calls a func passing async let as args but that will mean sometimes awaiting unnecessarily, like if you put them in a tuple (which i like a lot). is better to try to await the urgent ones first so some code can progress.
@alexito4 I don't have a lot of practice using async let, forget teaching about it. I need to get better at it!
@mattiem will this still work after the “inherit isolation by default” pitch?

@markmalstrom Hard to say for sure because some details there aren’t finalized yet. But I’m pretty sure yes.

I still find this incredibly verbose though…

@mattiem you find what incredibly verbose?
@markmalstrom So many keywords to express such a straightforward thing!
@mattiem the `async let` keywords or the stuff like `@concurrent` that’s coming with the inherit isolation pitch?
@markmalstrom I’d like a very lightweight way to move synchronous work to the background and await the results. Not quite possible today but I’m getting more hopeful now.
@mattiem I think I’m still a little confused 😅 `async let` seems very lightweight to me. What would you like to change or add?

@markmalstrom I think I want something more like the example at the bottom of this post.

https://forums.swift.org/t/pitch-inherit-isolation-by-default-for-async-functions/74862/119

[Pitch] Inherit isolation by default for async functions

I'm genuinely surpised to hear these suggestions, because my experiences have been exactly the opposite. I haven't yet encountered a situation where I've wanted some kind of stateless background type. But I have regularly found places where I have this one tiny bit of synchronous work that can be expensive and can and should be done off-actor. JSONDecoder is a fantastic, real-world example. This is the critical concept. I was just imagining what would happen if we made this change without t...

Swift Forums
@mattiem ahhh I see, one statement instead of two

@mattiem @markmalstrom

Does this do what you’re wanting? (It wouldn’t have some of the subtler benefits of child tasks, but otherwise I think just adding ‘Task’ and ‘.value’ to that example makes it work.

let result = try await Task {
// this runs on the global executor
try nonIsolatedExpensiveWork()
}.value

@bjhomer @markmalstrom Hmm, I don’t think so. This will inherit the isolation of the calling context, so it will only run on the global executor if that’s already where you are.

@mattiem @markmalstrom I love the easy readability of

let result = try await {
// this runs on the global executor
try nonIsolatedExpensiveWork()
}

Maybe even not so much the executor aspect of it, but just being able to start the async closure straight away like that.

Could the executor be specified similar to what we do with MainActor now? Something like this

await { MainActor in … }

await { GlobalActor in … }

await { CurrentActor in … }

@pasi You can get really close with an immediately-invoked closure today! But what you cannot do is work in the background.

https://mastodon.social/@a_grebenyuk/113206223567233398

@mattiem I'm confused about what's happening here - you're saying that merely writing `async let model = code()` will run code() on another thread even if it's not an async function?

@nicklockwood Yes!!

async let will use the global executor *if* the function is non-isolated.

@mattiem @nicklockwood That would change if the new proposal was accepted, wouldn’t it?
@mattiem how did you learn this?
[Pitch] Inherit isolation by default for async functions

I'm genuinely surpised to hear these suggestions, because my experiences have been exactly the opposite. I haven't yet encountered a situation where I've wanted some kind of stateless background type. But I have regularly found places where I have this one tiny bit of synchronous work that can be expensive and can and should be done off-actor. JSONDecoder is a fantastic, real-world example. This is the critical concept. I was just imagining what would happen if we made this change without t...

Swift Forums
@mattiem You keep on forgetting the trick it seems ;)
https://mastodon.social/@layoutSubviews/111089447958088298

@layoutSubviews Heh.

But I have learned more since then! Task.detached has a lot of side-effects you almost never want. I need a new tool!