Skip to content(if available)orjump to list(if available)

Swift-erlang-actor-system

Swift-erlang-actor-system

45 comments

·July 22, 2025

bcardarella

I'm involved with this project and wanted to provide some context. This is an extraction for a much larger effort where we're building a web browser that can render native UI. Think instead of:

`<div>Hello, world!!</div>`

we can do:

`<Text>Hello, world!</Text>`

I want to be clear: this is not a web renderer. We are not rendering HTML. We're rendering actual native UI. So the above in SwiftUI becomes:

`Text("Hello, world!")`

And yes we support modifiers via a stylesheet system, events, custom view registration, and really everything that you would normally be doing it all in Swift.

Where this library comes into play: the headless browser is being built in Elixir to run on device. We communicate with the SwiftUI renderer via disterl. We've built a virtual DOM where each node in the vDOM will have its own Erlang process. (I can get into process limit for DOMs if people want to) The Document will communicate the process directly to the corresponding SwiftUI view.

We've taken this a step further by actually compiling client-side JS libs to WASM and running them in our headless browser and bridging back to Elixir with WasmEx. If this works we'll be able to bring the development ergonomics of the Web to every native platform that has a composable UI framework. So think of actual native targets for Hotwire, LiveWire, etc...

We can currently build for nearly all SwiftUI targets: MacOS, iPhone, iPad, Apple Vision Pro, AppleTV. Watch is the odd duck out because it lacks on-device networking that we require for this library.

This originally started as the LiveView Native project but due to some difficulties collaborating with the upstream project we've decided to broaden our scope.

Swift's portability means we should be able to bring this to other languages as well.

We're nearing the point of integration where we can benchmark and validate this effort.

Happy to answer any questions!

sghiassy

I prototyped this exact thing by parsing the DOM, building corresponding UIKit elements and styling them via CSS.

My output looked exactly like an embedded WebKit UIView though - so then the problem became - what was I making that was appreciably better?

junon

Sounds like things are converging more or less where I thought they would: "websites" turning into live applications, interfacing with the native UI, frameworks, etc. using a standardized API. Mainframes maybe weren't the worst idea, as this sort of sounds like a modern re-imagining of them.

The writing was more or less on the wall with WASM. I don't know if this project is really The Answer that will solve all of the problems but it sounds like a step in that direction and I like it a lot, despite using neither Swift nor Erlang.

ksec

>If this works we'll be able to bring the development ergonomics of the Web to every native platform that has a composable UI framework.

Holy this will be much bigger than I thought! Cant wait to see it out.

null

[deleted]

tiffanyh

bcardarella

XAML will be a target as we intend to build a WinUI3 client. Of the big three native targets: Apple, Android, Windows the later may be the easiest as from what I've seen nearly everything is in the template already

aatd86

I believe swiftUI doesn't give access to the UI tree elements unlike UIkit. So I assume you're not allowing the use of the xml-like code to be in control of the UI? It's rather just an alternative to write swiftUI code? How do you handle state? Isomorphically to what is available in swiftUI? Is your VDOM an alternate syntax for an (Abstract) Syntax tree in fact? Is it to be used as an IR used to write swiftUI code differently?

How is it different from Lynx? React Native? (probably is, besides the xml like syntax, again state management?)

Quite interesting !

bcardarella

That's correct, but we can make changes to the views at runtime and these merge into the SwiftUI viewtree. That part has been working for years. As far as how we take the document and convert to SwiftUI views, there is no reflection in Swift or runtime eval. The solution is pretty simple: dictionary. We just have the tag name of an element mapped to the View struct. Same with modifiers.

bcardarella

As far as how it is different from React Native. That's a good question, one that I think is worth recognizing the irony which is that, as I understand it, without React Native our project probably wouldn't exist. From what I understand RN proved that composable UI was the desired UX even on native. Prior to RN we had UIKit and whatever Android had. RN came along and now we have SwiftUI and Jetpack Compose, both composable UI frameworks. We can represent any composable UI frameworks as a markup, not so much with the prior UI frameworks on native, at least not without defining our own abstraction above them.

As far as the differentiator: backend. If you're sold on client-side development then I don't think our solution is for you. If however you value SSR and want a balnance between front end and backend that's our market. So for a Hotwire app you could have a Rails app deployed that can accept a "ACCEPT application/swiftui" and we can send the proper template to the client. Just like the browser we parse and build the DOM and insantiate the Views in the native client. There are already countless examples of SSR native apps in the AppStore. As long as we aren't shipping code it's OK, which we're not. Just markup that represents UI state. The state would be managed on the server.

Another areas we differ is that we target the native UI framework, we don't have a unified UI framework. So you will need to know HTML - web, SwiftUI - iOS, Jetpack Compose - Android. This is necessary to establish the primitives that we can hopefully get to the point to build on top of to create a unified UI framework (or maybe someone solves that for us?)

With our wasm compilation, we may even be able to compile React itself and have it emit native templates. No idea if that would work or not. The limits come when the JS library itself is enforcing HTML constraints that we don't observe, like case sensitive tag names and attributes.

What about offline mode? Well for use cases that don't require it you're all set. We have lifecycle templates that ship on device for different app states, like being offline. If you want offline we have a concept that we haven't implemented yet. For Elixir we can just ship a version of the LV server on device that works locally then just does a datasync.

wmanley

How does this compare to hyperview? https://hyperview.org/

bcardarella

Not sure as I haven't done any work with it. On a cursory glance it could have some overlap but it appears to not target the 1st class UI frameworks. It looks to be a UI framework unto itself. So more of a Flutter than what we're doing is my very quick guess. We get major benefits from targeting the 1st class UI frameworks, primarily being we let them do the work. Developing a UI native framework I think is way way more effort than what we've done so we let Apple, Google, and Microsoft to decide what the desired user experience is on their devices. And we just allow our composable markup to represent those frameworks. A recent example of this is with the new "glass" iOS 26 UI update. We had our client updated for the iOS 26 beta on day 1 of its release. Flutter has to re-write their entire UI framework if they want to adapt to this experience.

arcanemachiner

> some difficulties collaborating with the upstream project

Can you elaborate on this?

carson-katri

I worked on this project, thanks for sharing it! This is part of a larger otp-interop GitHub org with projects for things like running BEAM on mobile devices, disterl over WebSockets, filtering messages sent over disterl, etc. Happy to answer any questions about the project.

__turbobrew__

I have read the erlang/OTP doesn’t work well in high latency environments (for example on a mobile device), is that true? Are there special considerations for running OTP across a WAN?

bcardarella

I hate to say this but usually when I hear that people have problems making Erlang/Elixir fast it comes down to a skill issue. Too often devs explore coming from another language and implement code as they would from that other language in Elixir and then see it's not performant. When we've dug into these issues we usually find misunderstandings on how to properly architect Elixir apps to avoid blocking and making as much use of distribution as possible.

__turbobrew__

Ok, I just ask because I recently read the “BEAM book” and it explicitly calls out that OTP is designed to only run within a single datacenter.

carson-katri

In our use-case, we're running the client and server on the same device. But if you're connecting a mobile device to a "server" node, you would probably want to be careful how you link processes and avoid blocking on any calls to the client.

rozap

This is neat. I'm not a swift user, but I did work on a project where we made heavy use of JInterface (which ships in OTP), which is effectively the same thing but for JVM languages. It worked great and allowed easy reuse of a bunch of Java libraries we already had. Pretty painless interop model, imo.

cyberax

One thing for which I can't get a clear answer: Swift uses automatic reference counting. It seems that it needs atomic operations that are expensive.

Does this allow somehow to sidestep this? Since the data is all thread-local, it should be possible to use non-atomic counters?

liuliu

Only "class" object is reference counted (to some extents, class-like objects too). Int / struct (value-semantics) objects are not reference counted. These are copied eagerly.

Swift introduced bunch of ownership keywords to help you to use value objects for most of the needs to sidestep reference-counting and minimize copying.

Of course, to my understanding, "actor" in Swift is a "class"-like object, so it will be reference-counted. But I fail to see how that is different from other systems (as actor itself has to be mutable, hence, a reference object anyway).

brandonasuncion

And for times you need a fast heap-allocated type, Swift's Noncopyable types have been pretty great in my experience. Especially so for graph data structures, where previously retains/releases would be the biggest slowdown.

Example here: https://forums.swift.org/t/noncopyable-generics-in-swift-a-c...

An added plus is that the Swift compiler seems to stack-promote a lot more often, compared to class/ManagedBuffer implementations.

Someone

In addition to what others said, even if counts have to be updated, that need not always use atomic operations.

See https://dl.acm.org/doi/10.1145/3243176.3243195:

“BRC is based on the observation that most objects are only accessed by a single thread, which allows most RC operations to be performed non-atomically. BRC leverages this by biasing each object towards a specific thread, and keeping two counters for each object --- one updated by the owner thread and another updated by the other threads. This allows the owner thread to perform RC operations non-atomically, while the other threads update the second counter atomically.“

(I don’t know whether Swift uses this at the moment)

llm_nerd

The Swift compiler does lifetime and ownership analysis to eliminate many/most ARC overhead, beyond when things are truly shared between threads and the like.

https://g.co/gemini/share/51670084cd0f - lame, but it references core concepts.

cyberax

I'm not sure how it can detect that outside of trivial cases. Any object that is passed into a library function can escape the current thread, unless the compiler can analyze all the binary at once.

slavapestov

> Any object that is passed into a library function can escape the current thread,

In Swift 6 this is only true if the value’s type is Sendable.

llm_nerd

Many (and increasingly most) Swift libraries are largely Swift modules delivered as SIL (Swift Intermediate Language). The compiler can indeed trace right into those calls and determine object lifetime and if it escapes. It is far more comprehensive than often presumed.

Though the vast majority of cases where ARC would come into play are of the trivial variety.