The Rye programming language is a dynamic scripting language based on REBOL’s ideas, taking inspiration from the Factor language and Unix shell. Rye is written in Go and inherits Go’s concurrency capabilities, including goroutines and channels. Recently, Rye gained support for Go’s select and waitgroups.

concurrency examples screenshot

Building blocks


Goroutines are lightweight threads of execution that are managed by the Go/Rye runtime. They operate independently, allowing multiple tasks to run concurrently without blocking each other.

Creating a Goroutine in Rye is straightforward. The go keyword is used to launch a new Goroutine, followed by the Rye function to be executed concurrently. For instance, the following code snippet creates and starts a Goroutine that prints a message after a delay:

; # Hello Goroutine

print "Starting Goroutine"

go does {    ; does creates a function without arguments
	sleep 1000
	print "Hello from Goroutine!"

print "Sleeping for 2 seconds"
sleep 2000


Starting Goroutine
Sleeping for 2 seconds
Hello from Goroutine!


Channels play a crucial role in coordinating Goroutines and ensuring data exchange between them. They act as a conduit for messages, ensuring that data is transferred between Goroutines in a safe and predictable manner.

To create a Channel in Rye, use the channel constructor function followed by the buffer size. For example, the following code snippet creates a Channel with no buffer:

; # Hello Channel

chan: channel 0

go-with chan fn { c } {
  print read c

sleep 1000
send chan "Hello over Channel!"
sleep 1000

Prints after 1 seconds and sleeps for another:

Hello over Channel!

To send data through a Channel, use the send method. The receiver side uses the read method to read a Rye value from the Channel.


The select statement is a tool for managing multiple Channels and responding to data availability. It allows Go/Rye to wait for data on multiple Channels and take action accordingly.

The select statement takes a sequence of cases, each containing a Channel expression and an associated block of code. When data becomes available on a Channel, the corresponding case is executed, allowing the Goroutine to handle incoming data from multiple sources.

; # Hello Select

c1: channel 0
c2: channel 0

go fn { } {
    sleep 1000
    send c1 "Hello"

go fn { } {
    sleep 2000
    send c2 "Bonjour"

loop 2 {
  select {
    c1 { .printv "Channel 1: {}" }
    c2 { .printv "Channel 2: {}" }

Prints first line after a second, then second after another and exits:

Channel 1: Hello
Channel 2: Bonjour

Select also supports a default case, as you will see in the example below.


Waitgroups are a way for goroutines to synchronize their completion. They allow a goroutine to wait until a set of other goroutines have finished. With waitgroup you can

; # Hello Waitgroup

wg: waitgroup

work: fn { id } {
    printv id "Worker {} starting"
    sleep 1000
    printv id "Worker {} done"
    wg .done

loop 5 { :i
    wg .add 1
    go-with i ?work

print "Waiting for workers to finish"
wg .wait
print "Finished!"

Prints with second delay betweet worker starting and done:

Worker 1 starting
Worker 2 starting
Worker 3 starting
Waiting for workers to finish
Worker 4 starting
Worker 5 starting
Worker 1 done
Worker 3 done
Worker 2 done
Worker 5 done
Worker 4 done

Example: Every Second News

In this example, a goroutine is spawned that generates two breaking news messages and sends them to a channel named c.

The main script uses Select to continuously monitor the channel, but if there is no message in the channel, it uses the Select’s default block to print [no news] and then sleeps for 1 second.

; Every second news

news: channel 0

go fn { } {
    sleep 1200
    send news "BREAKING NEWS"
    sleep 2100
    send news "MORE BREAKING NEWS"

loop 10 {
    select {
        news { .print }
        _ {
            print "[no news]"
            sleep 1000

Output, aproximately line per second …

[no news]
[no news]
[no news]
[no news]
[no news]
[no news]
[no news]
[no news]

Example: Concurrent Downloader

This example creates a waitgroup wg to track the completion of download tasks.

And the function download that accepts URL as an anonymous injected argument. It prints a message about starting the download, calls generic get method on the URL (generic methods dispatch on the kind of the first argument), retrieves the length of the downloaded text and prints the Loaded message. Then it sends .done to waitgroup.

Script iterates over a list of URLs (sites) and utilizes the go-with function to spawn a goroutine, and add 1 to waitgroup for each URL.

After all download tasks are initiated, the script calls the wait function on the waitgroup, ensuring that the main script doesn’t exit until all download tasks have completed.

; # Downloader with a waitgroup

wg: waitgroup

download: fn1 {
	.printv "Started loading {}." ,
	.get .length? :len ,
	.printv "Loaded {}, Num of characters: " + len
	wg .done

sites: {

for sites { .go-with ?download , wg .add 1 }

print "Waiting..."

wait wg

print "Stopped waiting!"

Prints with delays between downloads starting and finishing:

Started loading
Started loading
Started loading
Loaded, Num of characters: 18509
Loaded, Num of characters: 6602
Loaded, Num of characters: 1582695
Stopped waiting!

Wg .Done

These examples demonstrate Rye’s ability to leverage Go’s native Goroutines, Channels, Select, and Waitgroups to implement concurrent tasks efficiently. I believe a dynamic scripting language with solid Go-like concurrency features has some benefits.

Additional Resources:

For more examples of Goroutines in Rye, visit the Rye examples repository on GitHub:

If this got you interested in Rye - the language, visit our website’s main page, or documentation that is sill being written Meet Rye.