Is Embedded Swift only intended for embedded devices?

I am using Embedded Swift rather than the full language for game development because I find it valuable to compile a small binary for a platform like wasm and have it load instantly on any browser. But this mode forces additional restrictions that I would say are bit much for this use case, like not being able to query the name of a class or use any.

There exist use cases for a more portable runtime which don't imply being as resource constrained as embedded software. Are those going to be considered eventually? Or is Swift strictly either an application or embedded language with no in-between?

3 Likes

Eventually, though not necessarily as part of "Embedded Swiift"—see the vision.

See also the following and related discussions on these Forums:

3 Likes

I see, thank you. I guess I'll have to get used to living without my existential types for a while

2 Likes

That's sort of the fundamental tradeoff though -- you're giving up certain dynamic features for the ability to generate a small standalone binary that does not depend on the Swift runtime.

In principle it would be great if you could pick and choose the level of dynamism you want instead of "all or nothing" like we have to today, and I don't think there is any fundamental reason we can't have that in the future, if someone wants to work on that.

However, existential types pretty much do require the full Swift runtime, because you need to be able to instantiate runtime metadata and then use the metadata to manipulate values in a completely abstract away.

15 Likes

At the very least I'd love to be able to use existentials with classes, since with how I use value types it's much easier to ensure all types are known at compile time, and some use cases I had for any like SwiftUI style stacks can be replaced with variadic generics instead (at least I hope they can, last time I tried they weren't fully implemented).

But with classes this is really annoying, it's hard to express certain things with only single inheritance.

Since embedded Swift supports vtable dispatch for classes, it could conceivably also implement class-constrained existentials using the same technique (that is, for protocols that have an AnyObject or an explicit superclass bound). Since a class instance is always a reference counted pointer, there's no abstraction over the value representation itself.

2 Likes

Would you mind describing a concrete example of the use case?

Right now I had issues with this protocol:

protocol Storable: Entity {}

class StorageEntity: Entity {
    var capacity: Int { 0 }
    private var storage: [any Storable] = []
    ...
}

In this case the protocol doesn't require anything so I could make it a boolean on all entities that they override, and have some code that ensures they can't be stored by other entities, but I like having this expressed in the type system (even if it's probably less efficient)

In some cases where the exact type is known I'd get a compile error if I tried to store it somewhere and it isn't allowed

Without dynamic casting and metatype support, though, the amount of information we'd need to abstractly manipulate values is still greatly decreased, since we just need the value witness information rather than a full unique metadata record. And since Embedded Swift isn't ABI-bound to require value witness tables, it should be able to use the new value witness bytecode implementation for most purposes. We still wouldn't be able to arbitrarily open existentials, only call methods that are direct requirements of the stated protocols that don't have any unbound associated types which can be dispatched through the witness table, but that should cover a wide range of use cases.

5 Likes

Would it also make sense to offer 'sealed' protocols at some point whose existential values use a representation that looks like a multi-payload enum?

17 Likes

I've been using a variation of embedded mode four some time, via the Swift for Arduino project, which has similar restrictions.

I've found in practice that most places where I wanted to use any can be achieved Ina different way with more concrete type definitions. For example, with your sample, it could be defined as:

protocol Storable: Entity {}

class StorageEntity<Element: Storable>: Entity {
    var capacity: Int { 0 }
    private var storage: [Element] = []
    ...
}

It loses the ability to contain literally any type in one bucket, so maybe that doesn't meet you use case. But in my experience there are ways to narrow your scope and work around those limitations.

2 Likes

Yes, generics wouldn't fit my use case sadly. A StorageEntity would be anything like a chest or backpack, and I think a chest that can only store, say, swords wouldn't be a very useful chest :slight_smile:

The reason I want to have Storable as a marker protocol rather than using inheritance and requiring that only Item subclasses can be stored, is that eventually I might want to for example put a cat in a backpack and inheritance would fall apart. (weird example but I think you see what I mean)


There's also places in my code where I could replace arrays of existentials with variadic generics.

So you are right, I don't need them, but I could avoid having to deal with more complicated code or fighting with inheritance

Yeah, that could be a challenge. I guess it depends how dynamic you want that to be.

Not sure how many types of objects you would be tagging, but you might be able to make use of an enum to bridge your Storable types. Something like:

class StorageEntity: Entity {
    enum Element {
        case item(Item)
        case cat(Cat)
    }

    var capacity: Int { 0 }
    private var storage: [Element] = []
    ...
}

May not be practical in your case.

3 Likes

With macros supported when cross-compiling in Swift 6.0, one could automatically generate such enums at compile time.

3 Likes

I was only testing my wasm and rendering code with a simple game jam game, and it was already taking 14.5s to build on an M2 Max. I wouldn't want to make this problem even worse by generating code.

Writing these enums by hand would probably make compilation slower by itself given how much this time increased with every feature I added.

1 Like

Is this 14.5s using Embedded Swift or using regular Swift?

Embedded Swift, I am unable to build normal Swift with new toolchains due to some incompatibility preventing the Darwin module from working. I am only able to compile for wasm.

Is embedded Swift slower to compile then?

Unclear to me, what I've seen so far is that compile times are comparable. There's reasons why Embedded Swift could be slower to compile (has to do more work, e.g. mandatory specialization) but also reasons why it could be faster (can be more lazy about actually compiling code based on what's in use)...

It may or may not maker compilation slower using enums. Using any Storable has a cost because the compiler has to do more complex type checking/call site resolution when calling functions with existential type signatures. Using enum would make that process simpler to resolve.

Sure, but that's when writing the enums manually, and handling it with macros or writing down a large number of enum cases is not something I want to do; I've been waiting for Xcode to finish indexing SwiftSyntax for about 15 minutes now. I would rather not :slight_smile: