Iced: A cross-platform GUI library for Rust, inspired by Elm
github.com
github.com
https://news.ycombinator.com/item?id=26958154
https://news.ycombinator.com/item?id=24919571
It's great to see GUI frameworks being designed in Rust, countering the trend of doing everything in Electron.
https://github.com/femtovg/femtovg
It's a decent starting point for trying to build your own toolkit.
I have recently added a wgpu backend but for now it lives in my fork https://github.com/adamnemecek/femtovg
run the demo with `cargo run --example wgpu_demo --release`.
Also join the femtovg discord https://discord.gg/V69VdVu
- GPLv3 - free
- "Ambassador" - free commercial license (after approval), in exchange for marketing as being built with sixtyfps and authorization to use your logo and feedback
- Normal commercial - paid
I kinda like the addition of the "Ambassador" tier. Seems like a fair exchange for an in-development framework, and would be a good option for people that are building a new product and don't yet have the revenue to justify the cost during the prototyping phase.
For the record, the rust community has mostly settled around dual-licensing under MIT/Apache for what you can call "foundational" crates ("libraries"), which are both far more liberal than the GPL.
Those are just my words, please don't judge someone based solely on someone else's summary, prefer first-party sources to form your own opinion. What it means seems pretty clear to me: https://sixtyfps.io/ambassador-program.html Just email them about your project and see what they say.
That's just your opinion, there's no such thing as a blessed license scheme by the community. GPL is a perfectly valid choice if you feel like it
I didn't state an "opinion." I'm stating an observable fact. And don't paraphrase what I said - my words are right there and you don't need to play games with what I said or didn't say: I never said anything was "blessed," I merely pointed out that by and large, the Rust community has - whether you like it or not - mostly settled on MIT/Apache 2.0 (as in, the de facto license). I even couched my statement very carefully as referring to "foundational" crates, even though - as a matter of fact - it applies to the majority of published open source crates regardless of whether or not you consider them "foundational."
You can see for yourself with this breakdown of crates on crates.io by license (scroll all the way to the right): https://gist.github.com/passcod/2e1983ce415b32bdf3a0
Different languages/communities built around common languages or frameworks tend to "settle" (there's that word again) on different licenses. These are typically (but not necessarily) the licenses that the language/framework itself is licensed under. In rust's case, the majority of what you might consider "foundational" crates - i.e. crates that are intended for consumption by the broader community to build the basis of other projects in the same way that you would depend on the standard library itself, be they runtimes, util libraries, etc. have, by and large, accepted either MIT or dual-licensed MIT/Apache2.0 as the default to start with, unless there's a compelling reason (such as starting a business around it!) to do otherwise. There's nothing wrong with picking a different license, it's just less likely for the community to converge around a more strictly licensed option. That's just the way the world works.
To wit: Here's a way to implement it with Python and Tkinter for an old school reactive GUI.
https://maldus512.medium.com/how-to-setup-correctly-an-appli...
I wanted React with Elm-like architectures in WX enough that I got annoyed and built one ^_^
The real issue in that example seems to be that the event loop needs to wait for both UI events and read/write readyness on the serial port. This could probably be handled either with createfilehandler[1] (although it doesn't work on Windows) or by dedicating a thread to handling the serial port and posting messages to the Tkinter event loop as suggested at the end.
[1] https://docs.python.org/3/library/tkinter.html#file-handlers
Is the community active at all in 2021 or is it just the result of having basically one person working on the language?
I think there is a slow shift toward community built tools such as lambdera. The language itself is pretty stable (and good, IMHO).
Most of the news are available on the discourse discourse.elm-lang.org
Personally, I find working with Elm's state management features is like heaven compared to dealing with Redux+Typescript's intensely noisy types.
Anybody have any experience on how this compares to ReasonML + React?
Answering your question, the difference is Elm has a more expressive type system than javascript, and will catch a lot of errors for you, with excellent error reporting from the compiler.
The layout of the code becomes less important, because the compiler will catch most errors (actually recommended in elm guides not to split in too many files until overwhelmingly necessary)
https://crates.io/crates/iced/versions https://crates.io/crates/iced-x86/versions
I'm not sure there's a naming conflict here. There is little overlap between x86 decoding and a GUI library. If software is eating the world, then it's similar to Giant Bicycles and Giant Food Stores.
> There is little overlap between x86 decoding and a GUI library.
I work on a graphical tool that visualizes x86 binaries! It uses iced (the decoder), and it could very easily end up using iced (the GUI library).
I think both can keep their names long-term.
struct Counter {
// The counter value
value: i32,
// The local state of the two buttons
increment_button: button::State,
decrement_button: button::State,
}
impl Counter {
pub fn view(&mut self) -> Column<Message> {
...
}
}
this looks like it holds state in the ui layer... wouldn't it be better to keep state in some separate state/model object (struct) and bind the ui to that instead?not doing it this way couples a lot of logic/state to the ui framework which means its much harder to switch ui frameworks later, not to mention testing the logic independently of the ui choice...
im asking because i hope to get some insight not a silent downvote
https://github.com/ensisoft/gamestudio/blob/master/uikit/win...
I only ask because if it doesn't use platform text input, that is a non-starter for mobile.
It also would mean having to reimplement platform behavior for text input, like being able to hold Shift on macOS while moving the cursor to change text selection.
Self-implemented keyboard input pretty much only works for the simplest use case in english-native countries.
https://www.cmyr.net/blog/rust-gui-infra.html and the precursor https://www.cmyr.net/blog/gui-framework-ingredients.html
Browsers are actually a great example of my favorite approach to cross-platform UI, because they sprinkle platform-native widgets throughout the canvas that they render, controlled by a platform-agnostic programming language. Which reminds me that people were trying to use webrender[1] to build native apps in Rust.
Keyboard and key event input is much harder than it first appears. From multiple platforms to multiple languages to multiple keyboard interface devices to multiple layouts, and you cannot assume that the operating system provides a workable abstraction on its own.
// pseudo-interface
class TextWidget {
...
WidgetEvent HandleMouseEvent(const MouseEvent& m, ...);
WidgetEvent HandleKeyboardEvent(const KeyboardEvent& k, ...),
WidgetEvent InputEvent(const InputEvent& i, ...);
};
// client code.
PlatformMsg msg;
while (GetMessage(&msg, ...)) {
WidgetEvent event;
if (IsKeyboard(msg)) {
event=widgets.DispatchEvent(MapKeyboardMsg(msg),...);
} ... {
if (NeedsIMEInput(event)) {
OpenIME(ExtractDetails(event));
}
}
The client application interfaces with the native/platform/synthesizer API/wrapper library to read/generate events from apps message queue/script/internet/whatever and maps those into platform agonistic events supported by the widget library and dispatches them into the library where the library decides which widget will receive them etc. The widget will react to the event and in the case of the text input widget will return back an event with the relevant information for invoking some IME system (in this example). The client code interfaces again with the native/platform/synthesizer to do what is asked and then dispatches the result back to the widget which will update its state.This design will basically have the same functionality but will split the responsibilities so that the widget system would not depend on the input system. This allows the two to vary independently and remain agnostic of each other thus promoting reuse and testing and simplifying the implementation (IMHO).
The question is not about how to invoke some IME, it's about how to separate these concerns and de-couple the widget system from the input system by delegating the invocation (and required details) to somewhere else. I don't see any real reason that warrants tight coupling.
I think there are approximately five viable Rust toolkits: Iced, Makepad, sixtyfps, egui, and Druid (in no particular order). Any of these five could evolve to being a real solution, with enough investment. Of course, it's possible another could arise, or that I've given short shrift to one of the other contenders (areweguiyet has a list, but that resource is not super well maintained).
There are some things easier to do in Iced than Druid (especially integration with 3D, which is something we currently don't do at all). On the other hand, we're pushing pretty hard on infrastructure: proper text handling, input methods, subwindows, etc. Currently, Druid relies on platform capabilities for 2D drawing, but my main work right now is a new GPU renderer. And we hope to work with 'mwcampbell on accessibility in the next year (integrating AccessKit).
We've got a great community, but tend not to toot our horn very loudly, so people might not be as aware of the progress we're making. By contrast, sixtyfps is a commercial product (in addition to a GPL release) and has weekly updates.
Thanks for the detailed answer. As an aching Linux user, I appreciate your focus on infrastructure and accessibility.
Here's an example of Druid rendering a button. https://github.com/linebender/druid/blob/master/druid/src/wi...
Have you done any benchmarking on how this compares with Electron on things like binary size, memory usage? Those are usually most common complaints people have with regards to Electron so would be nice to see the improvements this provides.
From what I understand, this will avoid that problem by compiling to whatever native platform you are building for? Also it'd not just be a webapp in a shell.
For some people in some senses, there's a big advantage to the 'webapp in a shell' model, because HTML/CSS is considered good, or WhizzBang.js is the familiar tool for building UIs, or whatever.
I didn't mean to say it's better (or worse) than this - it's just quite a different (more similar to Electron) approach, that'll suit some and not others.
Electron is hundreds of megabytes for "hello world."
And then accessibility brings with it the integration with different OSes via disparate APIs, so it will take some effort to offer a cross-platform API to the library user.
You'll notice that Iced is not >= 1.0 yet, so you shouldn't expect it to be fully-baked.
Neat! I really like the Rust ethos of creating modular cross-platform adopters (e.g. winit, getrandom), and from a brief skim this seems to fit that model.
It’s out there, so whether you finish it, someone forks it, or it just serves as inspiration for another project, you’ve contributed.
When you have an accident waterskiing and end up on crutches, you'll be glad for the accessibilty of doors that open with a button press. When you poke your eye hiking and need to wear an eyepatch for a few weeks, a screen reader might not seem such a niche use case.
These are even kind of extreme examples: frankly, even stuff like dark mode that we take for granted at this point is an accommodation to how some people need or want to use their computers. Migraines are a good example of an (astonishingly) common condition that benefits from flexibility around having to stare at a screen.
None of us gets younger as the days go by, either, and that comes with changed needs. I'm still thirty years from retirement and I find that the 10 point font I used to prefer isn't good enough anymore. Bumping up text size is a quotidian action for me and millions of other people.
ADDITION: Ah, finally found the quote I was looking for. Maybe not directly relevant, but thought-provoking:
> SMS texting was invented [to] figure out a way for deaf people to communicate with one another without speaking. ... Now text messages are universal.
https://blog.ai-media.tv/blog/why-designing-for-accessibilit...
I am person, who in last few days tried out to use VoiceOver and move responsibility on it to navigate me and read contents, when I could code while I had closed eyes. Ironically the most annoying interface was CLI, because I had to extend my focus on contents of the terminal. Imagine running docker-compose up and you are going to spammed with reading all logs. Next, usually I had problem to find some relative point that I could mark a "milestone" in my head, which will be further point from I will be reading.
Also, not all commands are read after executing them. Such command is silly pwd. Everytime I write it down I had to move the VO cursor to read the contents. Also many characters I would recognize as a programmer VO reads differently. Nano editor was complete unusable for me. While in vim I had notable problem to recognize indentations characters (sometimes they are read and sometimes they don't) and to know in what mode am I actually. brew update+upgrade was a nightmare for me. All kind of separators like #### are read as "number", "number" spam. The same situation was with "clear" command. It was read a hundreds of "space", "space", "space"... I was starting to think I am going to the Moon or I play Portal 2 [0].
I could continue writing down my disappointment about the CLI interface that was among my favorites in past but now after this week with VoiceOver I think it is terrible to be blind, because most of time I had to use my eyes to help myself to recognize where I am and what I need to click to move to next element. I was very surprised by the rofi-like find of elements that are on the screen (named as Rotor feature, CapsLock+U). With VO I don't any longer needed to use mouse, which for anybody who has problem with wrists is also a nice thing to consider.
1. All your actions are in one place. Figuring out what can happen as a result of a series of user interactions can be done without navigating through a component hierarchy.
2. The history of a user interaction is simple to serialize. In case of error, you can dump the user interaction history for this session (just a list of enum values, right?) to disk. This can then be submitted in bug reports for easy re-production of an issue.
3. It's simple(r) to create a time-travelling debugger that can move backwards and forwards in time, since it can simply re-play the series of messages to a specific point.
4. Since you've seperated state changes from the UI, writing unit tests that ensures that a series of user interactions results in a specific state is pretty easy. You just send in the enum values representing a specific use interaction then check what state you've ended up with.
I'd imagine it would be easier to just call a method on whatever we want to update, instead of lowering all our methods into enums. Why wouldn't we do that instead?
Also, serializing usually isn't enough to get the benefits you describe. You need true determinism for that, and that's very difficult to achieve. If you ever e.g. query the current date without recording it you've just introduced nondeterminism.
If we could really achieve deterministic replayability easily, then I might be okay with the drawback of turning methods into enums.
Easier how? The difference in lines of code is minimal. It's not going to make any meaningful difference in how much time you spend on crafting a solution.
> Also, serializing usually isn't enough to get the benefits you describe. You need true determinism for that, and that's very difficult to achieve. If you ever e.g. query the current date without recording it you've just introduced nondeterminism.
It requires some thought, but it isn't terribly difficult. Most user interactions with a gui doesn't require a side effect, and those that do can usually be written in a way that it gets represented in the enum. Even when nondeterminism creeps in, it rarely cancels out all of the benefits of having a history of user interaction. Perfect nedn't be the enemy of good.
This is a common challenge with e.g. RTS games, and it means we have to very carefully discover and avoid nondeterministic functions.
For example, did you know that C#'s string's hash code calculation is nondeterministic across runs? The same can be said for any floating point calculations, iterating Rust's/Go's default hash maps, etc.
Also, IME most interactions with GUI do have side effects, GUI apps tend to be very stateful.
I do, but I also know that in Java, C#, Rust and Go it's well documented that you should not rely on the iteration order of the values in a hashmap or hash-based collection, which (at least in Go) is one of the reasons hash-iteration order is randomized across runs.
If a bug is caused by this kind of nondeterminism, you're likely to catch the bug by replaying the history multiple times. In other words, if the history isn't perfect due to nondeterminism, the history is still useful in tracking down bugs because that nondetermism will be revealed over multiple replays.
https://blog.sigplan.org/2019/12/30/defunctionalization-ever...
There are significant advantages to this sort of defunctionalization in a system like this. It is a bit more administratively at the point of enum definition, but the flexibility and simplification of the other parts of the program will be a win for almost any non-trivial UI application, not to mention the future-proofing it gives you.
And by the way, defunctionalization sounds a lot like transforming my code into a bytecode interpreter which interprets my code. This is something I don't need and which would make my code slower and more difficult to read.
If you don't care about your values leaving your local OS process, which is a pretty common use case, there's a hybrid approach you can take too, which is to put closures inside your data structures that describe the value, instead of passing raw closures around. You get the benefits of being able to examine the values without executing the handler and being able to filter, decorate, etc. but while retaining all the advantages of being able to implement handlers inline. Depending on your local language, various slick implementations may allow this to be one degree or another of transparent, such as implementing some sort of interface/trait for all these values, or implementing __call__ in Python, etc.
"And by the way, defunctionalization sounds a lot like transforming my code into a bytecode interpreter which interprets my code. This is something I don't need and which would make my code slower and more difficult to read."
It depends on the language you're in. Most OO languages have some way of doing this that doesn't add any speed issues to speak of, it's just a slight rearrangement of code.
I'd also say this is an issue of scale. I don't bother with this in small programs and may just glue everything together with closures, but as program size increases the odds that you'll want to do something that you can't do with closures directly increase. But the languages I tend to work in don't make this much of a hassle usually, either. I think you may be overestimating the expense.
The filtering you're talking about sounds interesting, but the GUI layer is (imo) definitely the wrong place to do it. When's the last time you wanted to filter, prioritize, secure, or distribute UI events?
Imagine if we transformed every method call in our app into enums, it would be an unwieldy and unreadable mess. I'm not saying you're suggesting that, just highlighting that there are definitely some drawbacks here.
So, I don't see why we're being forced to use enums in this case.
I wonder if this a conscious decision by the Iced folks, and if they first tried with closures and ran into some trouble.
Your hybrid idea is interesting! Is that doable with Rust in practice? I'd imagine the borrow checker might enforce it can't read from outside its scope.
By opaque, I mean the only thing you can do is execute them. While this is encapsulated, it isn't at the right level of control.
" but the GUI layer is (imo) definitely the wrong place to do it. When's the last time you wanted to filter, prioritize, secure, or distribute UI events?"
Ironically, the GUI is already that way. Under the hood, enumerated values were passed to your program by the windowing system. It has to be, because there's a serialization layer there. GUI programs already have no choice but to be structured this way, and indeed, also certainly have some sort of filtering mechanism in them to do things like ignore events it doesn't want to handle. You're probably programming at an abstraction layer that is already sitting above that. You think this isn't useful in a GUI precisely because the GUI framework you're using implemented this pattern for you already!
"Your hybrid idea is interesting! Is that doable with Rust in practice?"
Really it's just creating objects that have various inspectable attributes as needed, that also have some trait or something that allows them to be called in a uniform manner. It's not too hard, it's just a bit more structure around what is already there; you basically copy & paste the closure into the method of a new object, and make the capture explicit in the object.
This also seems like the wrong layer to serialize. Normally we would want to serialize the model, not the view. Admittedly, the line is a bit blurry here (and for most small programs) so take my words with a grain of salt.
I am, exactly -- to enable WYSWIG construction/editing of the GUI. Just added another comment saying this below, but consider Android Layout Editor/Apple Interface Builder (or someone else mentioned QT Creator, which I'm not familiar with but has the same idea).
There's only one MyFancyTextEditor widget definition, a blueprint so to speak. It's IDs refer to other things inside that blueprint.
But at runtime, we might instantiate two different MyFancyTextEditors, one for view A and one for view B. They probably shouldn't share IDs, lest we run into confusion.
I think WYSWIG is more like defining a class, whereas at runtime we'd be serializing instances.
Feel free to correct me if I'm wrong, I admit I've never been down this line of thought!
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ykl-6F-b5r">
<rect key="frame" x="0.0" y="0.0" width="375" height="0.0"/>
<state key="normal" title="Done"/>
<connections>
<action selector="doneEditingTitle" destination="POI-12-VR5" eventType="touchUpInside" id="14R-fR-990"/>
</connections>
</button>
A xib is an XML description of a GUI layout. Note the `<connections><action ...>` element inside the `<button>` -- this is defining what happens when the button is tapped. The connection becomes active at runtime when the file is rehydrated into live objects. The `selector` string identifies the name of the method to be called. (This exact mechanism relies on ObjC, but the principle can be applied to other systems.)(For the XIB system, they have no purpose at runtime; they're solely for the archive. Objects get their own identity as usual when they're instantiated.)
In practice, when we receive a GUI event, we then modify something in our surrounding environment, usually some model, controller, or other view. To interact with any of those in Rust, we need to refer to it by some sort of ID.
This is the same ID you're referring to, which objects get when they're instantiated, at least for views (models and controllers likely do something similar).
My central question is: why would we want to serialize those IDs?
Also, WYSWYG might not be the best example, as it's a GUI that produces another GUI, hence some confusion. If you think about this in terms of a simple app that maintains a Customer database, you'll see what I mean; we wouldn't ever want to serialize a button click event there, especially since the hydrated IDs aren't stable and we don't know what they refer to unless we serialize the entire app UI state.
You can create as many instances of `Button` as you want, and they are different, and their targets (the other widgets they message when tapped) are different because they were either created alongside or, if they were live before the archive was deserialized, a reference was made to them as part of the deserialization process.
Since we can't use references, we need to "refer" to other objects via IDs.
I thought you could take references to things in Rust. Also, in particular, I don't understand why you'd want to use closures -- inherently reference-y -- when everything else is (I guess?) a non-reference plain value.
And as a terminal-aficionado I don't want to use Design/Creator tools.
So ... what I really want is a simple API with closures. And there should be no problem with that, as the Design/Creator tools can simply emit more complicated code if they want (e.g. closures that use enums).
Yup that's exactly what people do in some GUIs. Tons of GUI frameworks and systems rely on varying degrees of defunctionalization and serialization. Microsoft COM and XServer for example. I don't know exactly how granular they get but I believe it's quite granular in what you can do over RPC.
Message passing at that level is fine IMO, but between GUI views/controllers sounds like a bad tradeoff though.
What you describe sounds like QT Creator, by the way.
For a pure function some kind of bytecode representation is certainly conceivable, but as soon as you add captures it gets much more complex.
> QT Creator
Precisely, yup; or Android's Layout Editor/Apple's Interface Builder.
So instead of:
Button::new(&mut self.increment_button, Text::new("+"))
.on_press(Message::IncrementPressed)
you would write: Button::new(&mut self.increment_button, Text::new("+"))
.on_press(|| MY_CODE_HERE)
For comparison, in JavaScript you can write: button.onclick = function(){ ++counter; }
That's quite a direct way of formulating what you want to happen. All in a single line and no Enums involved.[Edit: Ignore this next paragraph; as steveklabnik pointed out, this change has already been implemented and "Message(|c| c.value += 1)" is accepted by the latest stable version.] Incidentally, as long as there are situations where only function pointers can be used and not closures it would be really nice to have some support for anonymous function pointers in Rust (with the fn type and not just the Fn trait) so that one could write e.g. "Message(fn |c| c.value += 1)" instead of "Message({ fn f(c: &mut Counter) { c.value += 1 } f })". Or just infer the fn type for "closures" which don't actually close over any variables without the need for an extra keyword.
[0] https://github.com/nybble41/iced-counter2/blob/master/src/ma...
I mean, we're not really doing this anymore, right?
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0) {}In Rust, if you want to access shared state, you need to hold onto a reference to it. If a reference captured by a closure is unique, the compiler conservatively prevents you from not only accessing that variable, but anything that could transitively lead to it, and if it's shared, the compiler prevents you from mutating anything that could lead to it. Additionally, you can't move or destroy the object until there's no chance any value transitively referenced through it is still borrowed--which across threads, often means pretty much no borrow length will suffice. In a UI framework sending stuff across threads, that's a totally unacceptable state of affairs. By contrast, enums that declaratively specify what you want to modify have no such issues, because the state only needs to be accessed when you actually want to run the event.
There are three ways to work around the lifetime part, but they're not ideal. The most common is to use something like Rc or Arc, which allow reference counting (a primitive form of garbage collection). Reference counting always makes your objects shared, so it guarantees you'll have to tackle the mutation problem somehow, which can be unsatisfying; it also has a runtime cost (which can be quite significant) and can cause memory leaks if you have cycles in your data (though this is rarely an issue in UI frameworks). Secondly, you can architect your application around things like arenas, which give you "scoped" access to lifetimes--these can be really efficient with the right use case, especially pointer-spaghetti graphs, but can restrict a lot of the patterns you'd write in Rust, since the arenas have to outlive everything you want to do with the objects. Finally, you can separate your shared data from the closure--which works great but now you are restricted to "bare" functions or functions that only capture unrelated state. The closure can't specify the state it wants to update, it has to be directed to the object--which is much less ideal (since now the closures aren't universal in any way, as they need to be passed in the state they want to modify).
You can work around the mutation part in two ways, but neither is that great. One is, again, to ban closures from capturing any environment state, so the data is still stored separately and has to be passed in, which has the limitations mentioned previously.
The other is to use "interior mutability" which uses some combination of runtime checks and other restrictions to guarantee that you're only accessing the element one at a time. Almost invariably, these mechanisms are slower at runtime, restrict your types somehow (e.g. making them non thread safe), can cause your program to panic if you misuse them, or are just plain annoying to use (or some combination of all of these). So when you can avoid this option, you do!
So basically: enums make a ton of sense in Rust because they totally avoid these borrow checker issues. Your intuitions about using closures for this stuff in functional or garbage collected languages don't apply here--patterns that make a lot of sense in them don't work at all in Rust. That's one big reason there are so many Rust UI frameworks rather than just wrapping the C ones or using the same patterns you see in functional languages--the usual patterns simply don't work well.
That's not say there aren't some upsides to the approach from a reasoning standpoint. A big one is that it enforces "single writer" control flow--just one place is responsible for all the state updates, so you don't have to worry about synchronization weirdness due to lots of places updating the state on their own. IME, this helps a lot with reasoning. But not everyone agrees that that outweighs the convenience of being able to use a callback, which I totally get--it's just that the "convenient way" isn't really convenient in Rust at all. And it's this that causes all these libraries to be designed this way (at least that's been my experience), not theoretical concerns about serialization and stuff which I agree is basically a nonissue in practice.
I'm trying to make a language (https://vale.dev/) that addresses the borrow checker's limitations (by making it automatic and opt-in) to handle cases like these, if you want to drop by our discord (https://discord.gg/SNB8yGH), I'd love to get your thoughts!
Unfortunately Rust as a language pays the price for its flexibility here. By not mandating any particular memory management or interior mutability strategy, it makes even the safe cases like this (which are relatively "easy" to validate for safety) much more complex. So if your use case is in the intersection that Vala supports, I agree this can be a great point in the tradeoff space! FYI, you might want to check out old work on the MLton optimizing compiler, which also did automatic region inference to avoid garbage collection.
We don't use Cell implicitly, we instead rely on "type stability" for mutable aliasing, generational references for memory safety, and scope-tethering to enable zero-cost dereferencing.
Hadn't heard of MLton, checking it out now!
However, this can be quite restrictive depending on your definition of type stability (and what optimizations are allowed); for instance, Rust considered changing a value to null to be a structure altering mutation and is able to optimize around that, while it sounds like Vale cannot.
The rest of it sounds a lot like Rust (though I'm not sure whether regions are more like arenas or lifetimes, I'd have to look at the implementation, but generally speaking people call the same kinds of things regions even if they weren't trying to coordinate, and the performance tradeoffs you're describing sound really similar to those of regions in other languages). Overall, like I said, it sounds like a very interesting point in the tradeoff space!
Edit: Also, I misspoke when I said MLton, what you actually want is the similarly named sister project MLkit (http://www.mlton.org/MLKit ; see http://www.mlton.org/Regions for details on why they did not end up going with this strategy).
Vale also considers changing between Some<T> and None<T> to be a type-unstable operation, and the automatic borrow checker (HGM) detects the possibility for that (because the field was marked non-final), to not do auto-borrowing for it. We might decide to use something called the Undead Cycle to help with the Opt<T> case, though it comes with interesting tradeoffs.
There's a lot more to Vale, such as how our region borrow checker can enable zero-cost structured concurrency without the Send/Sync requirements, and the better RAII support. In the end, the only similarities we really share with Rust are the syntax and (hopefully!) the speed.
Vale regions are tracked like lifetimes, and have a concept of immutable vs const vs readwrite to aid in optimization, but they come with an implicit interface pointer to the containing objects' allocator, similar to Odin but memory-safe.
Oh, MLKit! I've looked into MLkit a bit before, but wrote them off because their regions needed a backup GC, where Vale's regions don't.
Keep an eye out, we'll be posting a lot of articles to HN soon ;)
Well, that's not the case :) Both Cell and atomics let you mutably alias, in the sense most languages mean. They don't let you mutably alias in the Rust `&mut` / "restrict" sense, which is far more powerful and would be unsound in any language, but they do let you mutably alias.
> Vale also considers changing between Some<T> and None<T> to be a type-unstable operation, and the automatic borrow checker (HGM) detects the possibility for that (because the field was marked non-final), to not do auto-borrowing for it. We might decide to use something called the Undead Cycle to help with the Opt<T> case, though it comes with interesting tradeoffs.
That's interesting and good to know! I'm glad you are taking this seriously.
> There's a lot more to Vale, such as how our region borrow checker can enable zero-cost structured concurrency without the Send/Sync requirements, and the better RAII support. In the end, the only similarities we really share with Rust are the syntax and (hopefully!) the speed.
A lot of the stuff in Rust is also found in other languages :) In this case, it sounds to me like Send/Sync are thoroughly built into your language, to the point that annotations are not needed--which is very nice, but not completely unique (this is the basis for memory safety in C# and Java, after all!), but may be so when combined with regions. From what it sounds like, though, I'm having a very hard time telling the difference between regions and (generational) arenas--they sound like they have almost exactly the same tradeoffs. Perhaps I'm missing something?
Overall, seems like an interesting language, and I will definitely stay on the lookout.
In our closure, we probably can't have any RefCell or Rc or anything that could reach either of those things, because those can't be safely reachable by multiple (even read-only) threads.
And I think these closures can't contain references to the outside world? I might be wrong on this one. And maybe structured concurrency could help here, though RefCell and Rc might confound that.
How well do Rust closures work in practice? Have you found a good use for them?
Extremely far. The strict mutability rules are basically your downpayment for the easiest safe parallelism of any production language (that isn't purely functional, anyway). In particular the Rayon library (https://docs.rs/rayon/1.5.1/rayon/index.html) allows you to make your sequential code parallel by merely changing `.iter()` to `.par_iter()`, and experimentally offers comparable performance to best in class work stealing schedulers, often outperforming manual C and C++ implementations of the same algorithms.
> In our closure, we probably can't have any RefCell or Rc or anything that could reach either of those things, because those can't be safely reachable by multiple (even read-only) threads.
Assuming you're talking about Vale, I'm curious how you actually enforce this in practice. `Cell<T>` is basically an "overhead free" type in a single-threaded context, but it's not safe to access from multiple threads since it lets you mutate through a shared reference. To get around that, you either need to have a different type for shared references from other threads, or you need something like GhostCell, which separates permission to read/write to the Cell from access to the Cell itself. I would have to learn more about how your language works to say more.
If you're talking about in Rust, yes, you're correct. Using `Cell` or `RefCell` prevents use with automatic parallelization APIs, which is one big reason they aren't just done automatically for you (as they effectively are in many other languages). However, there are thread-safe mutability types with various tradeoffs: atomics in specialized cases where the type has native atomic operations, mutexes and reader-writer locks for thread safe concurrent mutable access, and APIs like GhostCell that use type-level trickery to make access safe without overhead, at the cost of a more complex API (something like GhostCell probably has the best performance tradeoffs for the UI case, but it is not very easy to use!). For `Rc`, your option is more straightforward: just use `Arc` and you are thread safe again (assuming the type you're protecting is).
> And I think these closures can't contain references to the outside world? I might be wrong on this one. And maybe structured concurrency could help here, though RefCell and Rc might confound that.
Rust closures can contain references to the outside world--and usually do! The references are just heavily restricted, as the text notes, to the point that it's hard to use them for callbacks (it's still possible, but there's a lot of ceremony).
> How well do Rust closures work in practice? Have you found a good use for them?
Hm, apparently I should have explained this up front, since apparently I did a bit too good a job explaining the downsides! Closures are used very heavily in Rust, almost everywhere in fact. They're a cornerstone of the Rust iterator API (probably the most commonly used trait in all of Rust), Rayon's parallelization API, numerous methods on Option, Result, etc., and are exploited by tons and tons of functions. Closures are very restricted, but you also get a lot as a result--they not only parallelize well, they are zero overhead abstractions that frequently inline to the same thing as hand-rolled SIMD (as noted here: https://twitter.com/badamczewski01/status/138634513508098458...) which means people are never worried about performance overhead when including them in abstractions. They can also be boxed, reference counted, etc. to erase their types in cases where you're okay with paying for dynamic dispatch (in which case they more closely resemble closures in typical languages).
So, they work extremely well in practice! What don't work well in practice in Rust are "callback registration" patterns in general. In fact, this is so much the case that I'd say finding a way to do things without callbacks is the main reason people need to refactor their applications for Rust.
One of the biggest problems in software engineering seems to be that requirements change all the time. So unfortunately I can't know beforehand whether Rust fits my usecase unless the scope of the project is very limited. I do like the safety guarantees of Rust but now feel that they might conflict too much with flexibility at some point.
An `update` does not always precede `view`. In fact, `view` is called first after initializing the application.
Thus, I believe the example in the `README` is following the proper order :)
I've had no issues with Iced on any other platforms, fwiw.
[1] https://github.com/hecrj/iced/tree/master/web [2] https://github.com/hecrj/iced/tree/master/wgpu [3] https://github.com/hecrj/iced/blob/master/ECOSYSTEM.md
And an outsider might have trouble following the conversation, but that would happen no matter what words are used. Whether we say "Elm" or "C++", the target audience knows what's meant and others wouldn't be able to follow either way.
All languages evolve over time, despite official bodies trying to reject foreign influence. (IIRC the French government has a department that tries to limit loan words from other languages, but good luck with that given the internet.)
So what's the actual problem with it? Only downside I see is google-ability and occasional conflicts when two projects use the same name. Both of those exist with any naming Scheme (ha!) and imho haven't really ended badly for projects or their users.
GUI state mutations are triggered by messages passed around the system. Messages can be generated from user interactions for by asynchronous subscriptions (network socket, etc).
Which is cool but a custom rendered UI would be cooler.