The Toit language is now open source
blog.toit.io
blog.toit.io
How about vs. ye olde Java VM running on your SIM-card? (!)
Lua - MIT license eLua - MIT license OpenResty - BSD style licenses
Could LGPL license limit it's growth and popularity?
No.
There’s a lot of misconceptions and FUD about the GPL family. So, as a clarifier: The LGPL does not restrict what the user can do with a product;[a] it only limits other developer’s. If another program wants to integrate Toit (or any LGPL based thing) into their product, they need to abide by the LGPL’s linking and open source[b] provisions. But a user can use an LGPL or GPL licensed product without restriction.
[a]: Despite what some developers may claim
[b]: It’s more “source available” but for the users only. Open source is just one way to fulfill that requirement
For reference gcc is GPLv3 but that doesn’t obviously apply to inputs/outputs of the program that same reference is in the context of a language like toit. [b]
[a] https://www.gnu.org/licenses/gpl-faq.en.html#LGPLv3Contribut...
[b] https://www.gnu.org/licenses/gpl-faq.en.html#CanIUseGPLTools...
Not really. A lot of people license their code as BSD in the hope that some massive company picks it up and they either get famous or a fat consulting contract. In reality what we've seen e.g. Amazon do is just copy the idea for themselves and pay their internal engineers. GPL gives you more force, you can get money out of them to remove its restrictions.
Rewrote everything in CircuitPython after 1500 loc of cpp madness, and living happily thereafter. I had to switch to a more powerful board though, it would be great to have lower requirements. Adafruit's M0 adalogger board has been perfect for me in every way except it does not have enough flash for CircuitPython.
Toit looks funky, but nice. They seem to already have a decent start at the ecosystem, with very basic libraries like drivers for HD44780 already made. Doesn't have everything that I need yet, but looks very promising.
I'm working on a flight controller, not IoT, so I don't need their serviceability API, but that's an interesting way to monetize. If I needed that, I can definitely see it being cheaper to pay the fee than to develop stuff like a reliable OTA update system myself.
Recently I've been digging into creating a pure Nim ecosystem for microcontrollers after discovering just how much overhead Arduino and other generic C approaches are. The benefit Nim has here is that it is incredibly strong at compile-time execution. This means that I can write succinct Python-looking code which compiles to tiny binaries with great performance.
My biggest project in it so far is a keyboard firmware for a keyboard split in half. The whole project compiles down to about 4Kb with all the code for the port expanders and the layouts and special key macros I use. For comparison the Arduino code to blink an LED over one such port expander was about 3.8Kb and MicroPython can't even compile a hello world for the board I'm using.
I'm also sceptical of HHLs on restricted devices i.e. mcus, but Arduino seems like the only game in town atm.
HHL?
One huge benefit Nim has in this category is that it compiles to C. If your board can run C, it can run Nim. And don't let the Nim GC fool you, it can be disabled, or switched to the more modern ARC version which runs fine on microcontrollers (and if you don't use garbage collected memory it doesn't add any code to your project). Since it compiles to C you can easily wrap libraries and compile for pretty much anything.
Zig can at least also wrap C code super easily, but I'm not sure how the overhead and targeting is for it.
And therein lies the rub. If you can throw more computational power and resources at your problem, then you should absolutely go for something with simpler abstractions that make better use of your time.
The problem is however not with C or C++, but rather inexperience in how to manage the control/flexibility these languages allow. You now have 50 ways to shoot yourself in the foot, instead of the otherwise handful. And, you also get the choice of a few bazookas.
> I have no idea how people write whole browsers and operating systems in this language.
They use best practices that avoid all of the aforementioned 50 ways.
I'm honestly surprised how well regarded Arduino software stack is given that its target audience is not exactly seasoned C++ developers. I guess most casuals have very simple programs, or just go straight for micropython / circuitpython.
Managing tree-shaped allocation trees is possible but mistake-prone, and Rust includes a sound checker to catch mistakes. (Rust pushes users towards using "writable xor aliased" pointers which is integral to this sound checker. "Writable xor aliased" is one of Rust's biggest strengths and weaknesses, and differs from both C++ and GC'd languages.)
Managing intrusive linked lists is common in C (I have less C experience than C++) and probably tractable as long as you don't carry around pointers which may dangle. Rust doesn't help write correct intrusive code, and worse yet Stacked Borrows (a proposed set of rules for pointer aliasing) interferes with attempts to write intrusive collections in unsafe code (https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a...). Maintaining intrusive sorted-tree maps and such is probably possible in C, but perhaps even trickier to get right (note that I've never built one myself).
I try to avoid aliased object graphs (multiple objects hold pointers to another object) when architecting greenfield projects, and it's often (but not always) possible. When I encounter pointer aliasing anyway (often in existing codebases), I usually fall back to shared_ptr (reference counting) because manual memory management requires borderline-intractable global reasoning to make sure every possible codepath never uses an aliasing pointer after the object has been freed through another pointer. (This is especially difficult on code I'm not wholly familiar with, because I either didn't build it myself or forgot the details.)
And if you have reference cycles, shared_ptr leaks memory, so you need to manually clear shared_ptr on destruction (error-prone and risks leaking), use weak_ptr (runtime overhead and boilerplate on every read, not just creation/destruction), or use raw pointers (error-prone and risks memory unsafety). Rust doesn't help you with reference cycles either, sadly (Rust makes mutating shared references painful, Rc leaks memory, and Weak has runtime overhead and boilerplate, but at least Rc/Weak aren't atomic like shared_ptr). A garbage collector makes your life a lot easier, at the cost of runtime pauses (often higher throughput than refcounting, but slowdown occurs in bursts).
----
C++ is intractable to write correct threaded code in. The language doesn't tell you whether a T& is visible from one or multiple threads (which determines whether you need to acquire a mutex to access it). And if 1 object has methods A and B both exposed through a public API (accessible from multiple threads), but method A (which acquires a mutex) calls method B, then method B must acquire the mutex when called from outside, but cannot acquire the mutex when called by method A unless it's a recursive mutex. The alternative is to make neither A nor B acquire the mutex, and require callers to acquire the mutex beforehand (which they may forget to do). I've written more about this at https://write.as/nyanpasu64/c-mutexes-are-easy-to-misuse-and....
I honestly don't think Python and Java are that much more tractable to write correct threaded code (note that Python has a global interpreter lock, and I have limited experience with Java threading). Neither distinguishes references to an object visible to one thread, from references to an object visible to multiple threads. And neither can force users to acquire a mutex before accessing an object visible to multiple threads, though Java's synchronized methods use recursive locks which can avoid the method A/B problem above. Also the consequences for threading errors are slightly less severe (corrupted data and exceptions instead of segfaults).
Rust is tractable to write correct threaded code. Its type system distinguishes &T (sometimes shared between threads, usually read-only), &Mutex<T> (cannot access T without taking a lock), and MutexGuard<T> and &mut T (obtained when you acquire a lock, provides unique access to T while you hold the mutex). This ensures you'll never forget to acquire a mutex, and have to go out of your way to try to acquire a second lock to a mutex you already own.
It’s very powerful if you need it, but if you don’t, it’s a lot more cognitive complexity for little gain.
They are software developers.
The GC by default only collects garbage once you don't have enough memory for the next allocation, I thought I might need to adjust that, but even though it triggers regularly in my code, it never seems to cause a noticeable slowdown. Despite "flight controller" sounding realtimey, the code is resilient to some reasonable latency.
One potential concern is that CircuitPython does not support user defined interrupts, but I haven't needed that personally. And I think you can still have those if you drop into C? Not sure.
At any rate, if it does get slower as I add more code, for me it's easier (and safer) to deal with that, than with subtle memory corruption foot guns in cpp.
It would be more straightforward to say "I have no idea how people write whole browsers and operating systems."
Based on a quick look at the file “interpreter_run.cc”, Toit’s VM appears to be a stack-based interpreter like MicroPython’s. It would be interesting to know what techniques are being used that enable Toit to be so much faster.
If you're into fast virtual method calls - also for dynamic languages - you might want to take a look at Karel Driesen's excellent PhD dissertation on the subject:
https://cs.ucsb.edu/sites/default/files/documents/1999-24.ps
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.34....
Perhaps a much more detailed blog post going over an example would help us understand why this stack is useful? Since you appear to have paying customers, clearly, there is an advantage, but it is not immediately obvious.
Also, that seems like an unacceptably steep BOM item for a consumer device. The MCU itself costs less in many cases over reasonable device lifetimes.
We made lots of small decisions to make Toit useful for IoT. One of the things we optimized for was making the program code ROMable, so you can leave it in Flash. This has subtle effects on the language design but is vital on small devices where flash is often much larger and more power efficient than RAM.
We also just wanted to make a modern language with decisions that are natural in 2021, but weren't well known when Python was invented. Things like nullable types, or a language server that gives you nice autocompletion in VS Code. This also affects language design: collection.size is better than len(collection) because you can type collection.<tab> and the editor will help you.
>I don't personally think that using a subset of an existing language is a pleasant solution for programmers. The language doesn't get a coherent design, and you don't know which parts of the language will be present.
..sounds exactly like the kind of justification I would come up with if I got excited about designing a new system. Something that has happened many times :-)
(Note: I am just joking here, I have tremendous respect for you and your team mates)
My most recent experience is with Numba, the python subset JIT, so it may be slightly different for micropython
Maybe what you're talking about is a combination of things. If you don't know what you can and can't do yet, then you're unlikely to recognize limitations. New languages also probably tend to have documentation that more completely specifies the language, versus subset languages where the implementation might be all there is to tell you what you can't do. In principle, a subset language is a new language that just happens to have familiar syntax and semantics.
It's the gap between expectation and reality, coupled with the productivity hit of often using some feature, then having to rewrite without said feature, that leads me to dislike subset languages. It's often ultimately a different language with only syntactic similarity.
What does performing an SPI etc operation look like? How will syntax for things like this map to MCU hardware? Eg built in to the language, or with HAL and peripheral access libs etc?
What makes it specifically targeted at IoT vice general embedded? RF support built into the standard lib? Security features, eg like nRF-53 and Cortex-M33?
If you just have an M5Stack Core2 and want to play with it, then you don't need to write a new driver though. Probably you want the examples from this package: https://pkg.toit.io/package/github.com%2Ftoitware%2Ftoit-m5s...
(The implementation of that package in the src directory is an example of using an I2C peripheral.)
Surely better to design the language with static types than to have to awkwardly tack them on later if it gets popular?
Documentation looks excellent though! Especially the language comparison.
Type annotations for locals, fields, and globals are written with a trailing `/Type`. Return types are written with `-> ReturnType`.
See https://github.com/toitware/toit-lsm303dlhc/blob/main/src/ac... for a file I recently edited.
When a type annotation is written, the compiler enforces it. It uses it for static optimizations, and dynamically checks that the type is correct.
When a type can be null, it has to be suffixed by `?`.
How thorough is this? Beartype is also on the front page right now, and its README contains an example of dynamic type checking in Python that takes over an hour to run [0].
Is Toit able to avoid being that slow?
[0] https://github.com/beartype/beartype#why-should-i-use-bearty...
Since these checks also allow some optimizations, the cost of the dynamic checks is maybe 10-15%. And that's before doing a global type-inference, which should remove many of the checks.
Many small decisions help keeping the system fast. The memory layout, the bytecodes, the FFI interface, ...
Separating the compilation process from the running process obviously also helps. It gives us the option to do slower optimizations before-hand, and not pay for them at runtime. (Independent of the fact that the ESP32 wouldn't be able to do big optimizations anyway).
This is actually an area we haven't really spent too much time on yet: optimizations. The current compiler doesn't even do inlining yet. The speed of Toit was never really a problem, and we preferred to spend time elsewhere. Eventually, we will definitely do more there again.
Look at objects.h for example. Or the interpreters
In all seriousness, though: if you like Go, then tinygo could be a good solution.
Personally, I find coding in Toit just a much more pleasant experience.
The Toit Programming Language - https://news.ycombinator.com/item?id=26365202 - March 2021 (18 comments)
Is the Toit language compilable to WebAssembly? I think it could be a great win for usage across different ecosystems!
I know for sure that the compiler compiled to it, but I'm pretty sure I also had a "hello world" that worked.
I would need to find that branch and see if it still merges cleanly.
Or did you compile the VM to WASM instead, and interpret the Toit code as normal?
It may be fast, but how does it compare to Nerves for highly-distributed, swarm or fault-tolerant networks using many IoT devices for distributed control and information and gathering?
If you have an ESP32 you need something like Toit, if you have a Raspberry Pi you might as well run embedded Linux (with Nerves on top).
I half wonder if the team felt any pressure to release the language now, ahead of Advent of Code...
https://datacvr.virk.dk/data/index.php?enhedstype=virksomhed...
(I will admit I thought the same thing, pleased to see it wasn't just me.)
We routinely run it on our desktops as well.