73 comments

[ 2.6 ms ] story [ 135 ms ] thread
Is Jaguar an interpreter and Toit the scripting language? How does it compare to MicroPython? Can I link in C drivers?
Jaguar is the tooling to enable rapid development. It consists of two parts: one that runs on the ESP32 and another one that runs on your computer.

Whenever a new program is ready, it gets pushed (over WiFi) to the device. The whole process only updates the actual program (not the whole OS), thus making the development cycle really fast.

Compared to MicroPython, Toit is significantly faster. In my (obviously biased) opinion, it's also more robust.

You can't link C drivers directly without modifying the Toit code base, but there is a bridge to talk between C and Toit code. Generally, we prefer to reimplement the drivers in Toit. However, for some bigger ones it clearly makes sense to keep the C version.

I like the syntax. It looks like a combination of Ruby and Python with a goal of being more terse than either.
I like it, but I am never going to be able to read the name without thinking of Goldmember saying "toit like a tiger".
I instinctively read the title of this thread in Goldmember's voice
"Toit, a languaj de-shined for microcontrolersh, yaysh"
(comment deleted)
I thought "get toit" or "get a round toit".
The syntax looks pretty fun to work with! Nice balance of terseness and readability.
Can't wait to hear all the mangled pronunciations of this.
Must be because I learned some French as a kid, but I can't help reading the "oi" as a "wa" sound in this context. Probably not what the authors were going for.
It's worth noting that Toit is developed by the team that developed V8, so it's got a really solid implementation by very knowledgeable people.
I wonder what the memory model is, and if it's suitable for low-level and real-time code.
Would be cool if it replaces javascript. It seems they have knowledge of almost all things needed.
They designed Dart before this.
What I learn from coding for a decade is mistake never makes perfect!
Language design is hard. It's impossible to please everyone. What Dart has taught me is that JavaScript is not going to be displaced on the web anytime soon. Dart seems to have found its stride with Flutter, a cross-platform UI framework.
I looked at this the other day! Sadly no way to configure WiFi other than at the point of flashing the device which rules it out for a lot of use cases.
Call me old-fashioned, but I personally do not like the idea of using a garbage-collected language for embedded development in general, and it's just flat out non feasible to use for anything which is timing-sensitive...
In big parts that's true, but there are some good arguments in favor of a GC for these small devices:

- the GC is a sliding compacting GC. As such there is no fragmentation for heap-allocated memory.

- the memory on these devices is tiny. The GC thus doesn't take long time.

- the ESP32 has really good peripherals, making timing sensitive operations very rare. In most cases there is a hardware module that does the job for you. The timing requirements then usually become significant less important.

depends on how the system works. What are the timing requirements? How powerful is the CPU? How much memory do you have? How often will the system be reset? Embedded isn't just 8/16 bit CPUs with < 128k of memory these days.
What do you think is the ratio between projects that need precise timing and projects that publish some sensor data every 60 seconds?

I think you can easily do 90% of iot projects with this. Which means you have more time for the 10% of projects that are actually hard.

If you're using LoRaWAN to publish and don't want to blow up your power budget, you need to get somewhat good timing.
From a functional PoV, yes, timing seems to be relaxed by publishing sensor data every 60 seconds.

From an embedded system PoV, your sensor might be a 1-Wire protocol device with a super strict signal timing. How do you mix both timing requirements? How do you differentiate embedded projects requiring "precise timing" from those others requiring "every 60 seconds"?

Unless Toit provides drivers for all kind of buses, sensors and peripherals AND it orchestrates the GC with the strict timing requirements. Otherwise it's probably a good fit only if it just runs on a known controlled development board/architecture/platform.

We rely heavily on the ESP32 peripherals for strict timings.

For example the 1-wire protocol is implemented using the RMT (remote control) peripheral. The pixel strips driver uses the UART or i2s.

If really necessary, we could drop down to C, but the hardware is usually better (more precise and leaves us to do other things in the meantime).

Exactly, that's why "it's probably a good fit only if it just runs on a known controlled development board/architecture/platform".

Maybe the title should be "Toit is a modern high-level language designed specifically for ESP32".

Literally every uC has hardware blocks for that, 99% of the stuff you will ever encounter is a standard. Nobody bitbangs protocols. And even if, the ws2816 driver on the raspberry pi is bitbanged from Linux user-space.

How often are you bitbanging non standard protocols? And if so, which field are you in?

> Literally every uC

Not true at all.

> How often are you bitbanging non standard protocols?

Regarding protocols: bitbanging 1-Wire. Not all microcontrollers have this peripheral. You might be lucky if you can adapt some other peripheral like an UART (with due effort). Or you can use for example an external ds2482 and drive it through I2C.

WS2812B: sometimes you can emulate it with some other peripheral, sometimes you have to do it by bitbanging some GPIO.

I don't bitbang so often in general. But it's not just about available "hardware blocks": introducing undesidered delays in embedded applications is a terrible, terrible idea.

You can have a perfectly working sensor through I2C with DMA, super optimized with 0 CPU intervention. This sensor has an ODR of 1kHz. A GC stopping the microcontroller for 400ms makes you stand behind 400 samples you might be processing in real time (FIFO aside, and because you know you shouldn't be doing any processing in an interrupt handler, right?). You don't lose the buffered samples of course, but your processing is now delayed.

Or perhaps you are doing some 44100, 16-bit stereo audio processing (in or out). You have implemented the perfect I2S/DMA mechanism and all that, combined with perfect SDIO reading. What would happen in a real-time processing audio application if the GC introduces even 20ms delays now and then? Horrible latency and/or artifacts... And because your MCU has 32KB of RAM you don't have the luxury of big buffers, so you have to be quick reading, processing and outputting. Stopping the processing = stopping the output.

Or you might be dealing with some external peripheral that needs an answer within a time limit...

You can see it's not just about "hardware blocks".

Following what I said before, even if "60 seconds" is the application required timing, there are other things that might be happening in the background that require precise timing.

Is Toit a bad language? I don't know. But as I said before, it only makes senses if ran under a known platform like ESP32 where the language developers have tuned everything, and the programer doesn't pretend real-time applications. A generic language for every microcontroller? I am not so sure...

I don't know about Toit, but many embedded applications will not allocate post-initialization time, in which case GC or not GC behave exactly the same with regards to timing.
There are algorithms for doing garbage collection in real-time systems, but these tend to have more overhead in total than stop-the-world GC.

On microcontrollers, which often have very limited RAM, I'd prefer avoiding doing any heap allocation at all. If there is any need for any dynamic allocations, they are within fixed-size memory pools — each with only fixed-size objects or with objects on a stack.

You're old-fashioned. It's not like malloc/free provide real-time guarantees either, but somehow we don't get complaints about C being unsuitable.
That's why you don't use it on small microcontrollers (8/16 bit). And if you absolutely have to, use it once on startup to implement a dynamic memory pool.
Many (most?) embedded C projects have a policy of no dynamic memory allocation.
I just searched for esp32 projects/libraries on github, the first 3 had malloc calls.

Maybe they don't do[1] dynamic memory allocation in code segments that have real time constraints, or after initialization, etc. (Most embedded projects actually don't have hard real-time constraints, or have them in extremely localized places like bit banging loops...) This is sounds like a good idea and is orthogonal to whether the language's dynamic allocation support is via malloc/free or gc.

Yeah fair. But I think that any gc language for microcontrollers should have a couple features:

1) A compiler flag or similar optional static check that throws a warning if you have dynamic memory allocation. In scripting languages it can be easy to do accidentally

2) A way to block the gc from happening in a time critical section of code, like how you can disable interrupts

3) Some guarantee on the maximum time gc will take

MicroPython provides a way to stop the GC (and an exception will be raised if memory is attempted to be allocated). This provides us with most of the control we require...
(comment deleted)
(comment deleted)
For those interested in a comparison to MicroPython: I recently wrote a Toit version of the MakePython ESP32 Dev Kit lessons.

Here is the Toit version of it: https://docs.google.com/document/d/1K-TYea7jbYfj2ecMUmr0T0zd...

And here the MicroPython: https://www.makerfabs.com/desfile/files/Get-Started-MicroPyt...

Thanks for sharing. Complex examples like these are great to highlight syntax which simpler examples never mention. For example (heart rate sensor excerpt):

    remembered_ = List width/2: initial_value
    half_height := height / 2
    ...
    min_value := remembered_.reduce: | a b | min a b
These lines (with seemingly context-sensitive meanings for the / and : characters) feel less obvious to me than the examples on the home page.
The `:` are actually doing the same thing, introducing a block (think of it like a limited function).

The `/` are also the same (division), but it's true that we also use `/` to introduce types. For example `x/int`.

I think Toit is relatively easy to learn, but there are definitely a few things that need explaining in the beginning.

Excuse my idle curiosity, but... slash for type ascription? How did that happen? The only precedent I can think of is Prolog’s arity annotations (and jq’s, but I suspect a direct influence there). Does this come from some chunk of programming-language lore I’m unaware of?
It’s also used in Elixir and Erlang for arity, but now that I think about it, it’s probably descended directly from Prolog.
We couldn't use `:` as that one is already used for too many things.

We then experimented with different tokens, and `/` felt best.

Wadler’s law or not, I’m not criticising, just curious :) Thank you (and 'EricCorry in sibling comment) for taking the time to answer!

[Yes, colons are nice as block introducers but don’t play nice with colons for type ascription, which is where we got Python’s asinine f(x: int, y: bool) -> str; don’t know why Rust went with that instead of ML’s uniform f(x: int, y bool): str.]

Answers from the implementors, awesome!
Part of it is that we already used colon for several things.

Also types are optional in Toit, so they are a bit like comments (though they are now checked so they are less like comments now). So they look a little like // comments.

It feels very Ruby-like. In fact, it makes me wonder why not reuse Ruby's syntax if you're making a thing like that.
Toit and Ruby are both inspired by Smalltalk, and there are certainly similarities in the syntax but we felt that starting 20 years later we could do a lot better than Ruby syntax.

In particular, Python basically won the significant-indentation argument. Since all programs are formatted with correct indentation the punctuation is redundant clutter. So for a new language it just feels right to go with indentation instead of curlies.

We also wanted to be free to add type annotations and other enhancements, so it was never going to be compatible with Ruby anyway. And we didn't want to raise expectations of Ruby-compatibility that would immediately be dashed.

As long as Toit is licensed under the GNU Lesser General Public License v2.1, I am unlikely to consider using it. By contrast, MicroPython is licensed under the MIT License (MIT).

As others have noted, Toit seems like a bad name. I suppose non-French speakers will tend to inadvertently mispronounce Toit.

Getting traction is hard. In my opinion, having a restrictive license and a bad name make it less likely Toit will gain traction. Finally, Toit's licensing reminds me of Mongoose OS https://github.com/cesanta/mongoose-os#mongoose-os---an-iot-....

What's the problem with the LGPL?
The GPL is for when you have an ideological agenda, the MIT is for when you don't. MIT says "do whatever you like with this but include the license" in less words than the GPL's preamble. GPL says people can use it, but can't without providing all the source code for no more than the cost of media (though I'd have to call a lawyer to be sure that's all it says these days because the damn thing is long and complicated) but it forces users to conform to someone's idea of fairness.

Basically if the thought of your code ever ending up in something that someone might somehow make money from, like maybe in an iOS app, is somehow icky - then GPL is for you! If you just want to put it out there and code, not politics, is your thing - go MIT.

Source... https://www.reddit.com/r/roguelikedev/comments/4g738z/commen...

If you just want to use the compiler and VM the license doesn't effect you. You can write a closed source program in any GPL language using any GPL compiler.
Thanks for taking the time to explain that to me.
I love the look of this language's syntax. I'd be happy to use it outside of embedded programming, too.
We use it for some of our tools, so it's possible (and works nicely). Our LSP server was initially written in Toit: https://github.com/toitlang/toit/tree/master/tools/lsp/serve... (To be fair, it uses the compiler underneath for analyzing the sources).

Main restriction is, that we haven't finished the Windows implementation yet. Specifically networking and spawning processes only works on Linux/macOS so far. (The main reason we ported the LSP server to Go).

The `host` package (https://pkg.toit.io/package/github.com%2Ftoitlang%2Fpkg-host...) provides some functionality for running things on the desktop.