Releases: NLnetLabs/roto
v0.9.0
Released 2025-10-15.
This is a minor release with regards to features, but it contains some important bug fixes and quality of life improvements.
Language
Breaking changes
- Enums and records can no longer be compared with
==and!=. This was
previously allowed, but the generated code might have been wrong. This
feature might be brought back with a proper implementation later. (#289)
Added
- Added
addr,min_addr,max_addr,lenandeqmethods toPrefix. (#289)
Bug fixes
- Fixed a case where we dropped an uninitialized value that occurred when an
expression that might return was assigned to a variable. (#280) - Fixed some miscompilations around using the
==and!=operators on
Prefix. (#289) - The
to_stringmethod of expressions in f-strings are now resolved later, which
avoids some cryptic error messages. (#281)
Crate
Breaking changes
Added
- Make the
RegistrationErrortype public. (#271) - Implement
std::error::ErrorforFunctionRetrievalError,
RegistrationError. Thanks @you-win! (#270)
Bug fixes
- Roto now works correctly again when it is compiled with static linking (such
as with the musl target). (#286)
v0.8.0
Released 2025-10-06.
This release features big changes to the registration API, making it easier to use and much more powerful. Additionally, it contains some bug fixes and documentation improvements.
Language
Bug fixes
-
The trailing comma of match arms is now optional, as it was supposed to be. (#253)
-
Fix an issue where the types of float literals weren't properly resolved. (#258)
Crate
Breaking changes
-
The functions
SourceFile::read,FileTree::read,FileTree::single_file
andFileTree::directorynow returnResultand will no longer panic on
I/O errors. Thanks @algernon! (#250) -
All custom registered types now always need to wrapped in
Val. This fixes
some bugs around registered types and should make it easier to understand when
Valhas to be used. Below is a diff illustrating how to migrate. (#247)
runtime
- .register_clone_type::<AddrRange>("A range of IP addresses")
+ .register_clone_type::<Val<AddrRange>>("A range of IP addresses")
.unwrap();
// Register the contains method with a docstring
-#[roto_method(runtime, AddrRange)]
+#[roto_method(runtime, Val<AddrRange>)]
fn contains(range: Val<AddrRange>, addr: Val<IpAddr>) -> bool {
range.min <= *addr && *addr <= range.max
}New
-
The CLI can now run other functions than
main. Thanks @pieterlexis! (#261) -
It is now possible to register closures. (#248)
-
There is an entirely new registration API using the new
library!macro.
The previousRuntime::register_*methods are all still available, but
deprecated. (#254 and #262)
use roto::{Runtime, Val, library};
let lib = library! {
/// A vector with `x`, `y` and `z` components
#[clone] type Vec3 = Val<Vec3>;
impl Val<Vec3> {
/// Scale this `Vec3` by `r` and return the result
fn scale(self, r: f32) -> Self {
Val(self.0 * r)
}
}
/// A vector with all zeroes.
const ZERO: Val<Vec3> = Val(Vec3 { x: 0.0, y: 0.0, z: 0.0 });
};
let rt = Runtime::from_lib(lib).unwrap();Documentation
New
- The generated documentation is now spread out over multiple pages. It
generates a page per module and per type. (#254) - There is a new "Syntax Overview" page in the documentation. (#263)
- Added the following concepts to the language reference: identifiers, booleans,
match, local variables and string formatting. (#263)
Bug fixes
- Fixed some examples so that they are consistent with the text and ensure that
they compile. Thanks @pieterlexis! (#260) - Fixed many spelling errors in the manual, API docs and the rest of the
codebase. Thanks @jsoref! (#264)
v0.7.1
Note: This release differs from
main, as it contains backported changed.
This is a small patch release on top of version 0.7.0. It is recommended to upgrade since there are no breaking changes.
Fixes
v0.7.0
Released 2026-08-25.
This release features a new documentation website available at https://roto.docs.nlnetlabs.nl. If you're new to Roto, it is a great place to start learning. It is a work in progress, so please give us feedback!
We have also made a tree-sitter grammar for Roto, that is now good enough to try out: https://github.com/NLnetLabs/tree-sitter-roto.
Language
Breaking changes
- The
functionkeyword was changed into thefnkeyword. (#213) - The
Optionaltype has been renamed toOption. (#216) - The
Unittype has been renamed to(). It can also be constructed with
()(#225)
New
import foo.{bar1, bar2};
import foo.{bar.fn1, bar2.{fn2, fn3}};
- Variants, methods and static methods can now be imported directly. (#202)
# Import variants
import Option.{Some, None};
# Import a method (as `append`)
import String.append;
# Import a static method (as `new`)
import Prefix.new;
fn foo() {
let x = Some(10);
let y = append("race", "car");
let z = new(1.1.0.0, 16);
}
- Methods can now be called as if they were static functions. (#202)
fn foo() {
# previously:
let x = "race".append("car");
# now also possible:
let y = String.append("race", "car");
}
- Roto now has its first looping construct:
while. (#194)
fn factorial(x: u64) -> u64 {
let i = 1;
let n = 1;
while i <= x {
n = n * i;
i = i + 1;
}
n
}
- A new
?operator, will return aOption.Nonevalue or yield the value insideOption.Some, much like the same operator in Rust. (#195)
fn small(x: i32) -> i32? {
if x < 10 {
Some(x)
} else {
None
}
}
fn foo(x: i32, y: i32) -> i32? {
let z = small(x)? + small(y)?;
Some(z)
}
- The unary minus operator has been added to negate signed integers and floating point numbers. (#196)
fn abs(x: i32) -> i32 {
if x { x } else { -x }
}
- It is now possible to add a type annotation on
letbindings. (#197)
fn foo() -> u64 {
let x: u64 = 10;
x
}
- Python-like f-strings have been added. This is a basic implementation that is still rough around the edges. All primitives and registered types with a
to_stringmethod can be put in a string prefixed withf. Any expression evaluating to such a type is valid. See issue #244 for the current limitations. (#220)
fn foo() -> String {
let x: u64 = 10;
f"The value of x is {x} and twice x is {2 * x}"
}
Bug fixes
-
Roto now has an additional compiler pass: MIR. A lot of the code generation was rewritten from scratch for this change. With the MIR we model values passed to Roto in a more robust and several memory safety issues were fixed in the process. (#182)
-
The error message for multiple item declarations with the same name have been improved, especially when two tests with the same name are declared. (#191)
-
The error messages for unresolved paths have been improved. (#198)
-
String literals are now properly unescaped. (#200)
-
The previous implementation of constants turned out to be unsound, so a new implementation should fix memory safety issues around them. (#208)
-
The
ReflectandRotoFunctraits are now sealed because they should not be implemented by downstream crates. (#214) -
Fixed a bug where it wasn't possible to use multiple
notoperators. (#195) -
Fixed an incorrect error message for
==that showed the wrong type as expected. (#242)
Crate
Breaking changes
- The CLI is now gated by a
clifeature flag. (#175) - Logging is now gated by a
loggerfeature flag. (#175) - The fields of
Runtimeare now private. (#193) Runtimeis passed by reference instead of moved to the compilation procedure, allow for easier reuse across multiple compilations. (#193)- Registered constants are now required to be
Send + Sync. (#208) - Registered constants are now required to have a valid identifier as name. (#223)
New
- There is a new
Compiled::get_testsmethod to get the tests in a Roto script without running them. This allows more flexibility in how the tests are run and what the output is. Thanks @algernon! (#185) - The
Runtime::add_io_functionsmethod adds aprintmethod to the runtime, making it possible to write"Hello, world!"to the terminal! (#193) Runtime::compileis a convenience function that will compile the script at the given path. It is no longer necessary to make aFileTreefirst. (#193)
Bug fixes
- Fixed some cases where it was impossible to return certain types from registered functions. (#210)
Other changes
- We switched from the
icucrate tounicode-identfor the parsing of identifiers. This shrinks the dependency graph ofroto. (#173) - We removed unnecessary files from the crate as published on <crates.io>. (#174)
Runtimehas improved documentation that was previously on the non-publicruntimemodule. (#193)
v0.6.0
Roto is the JIT compiled, strongly-typed, embedded scripting language for Rust used by Rotonda.
We're happy to announce Roto 0.6.0, which includes many bug fixes, quality of life improvements and an assignment operator!
We'd like to thank @algernon for opening 9(!) excellent issues over this release cycle. Many fixes and quality of life improvements were inspired by their findings. Thank you!
Breaking changes
- The minimum supported Rust version is now 1.84.
- The
Compiled::get_functionmethod no longer takes separate type parameters
for the parameter types and the return type, but a single function pointer
type. (#163)
// old
compiled.get_function::<Ctx, (bool, i32), Val<Verdict>>("main");
// new
compiled.get_function::<Ctx, fn(bool, i32) -> Val<Verdict>>("main");- Registered functions no longer take arguments of runtime types by reference.
Instead they takeVal<T>, mirroring how they are passed in from Rust to Roto.
(#138)
// old
#[roto_function(runtime)]
fn foo(x: &Foo) -> Foo {
todo!()
}
// new
#[roto_function(runtime)]
fn foo(x: Val<Foo>) -> Val<Foo> {
todo!()
}- The return type of registered functions now needs to implement the
Reflect
trait. (#163)
New
- Roto now features an assignment operator to update the value of a variable
or a field. (#158) - The CLI now has a subcommand
printto print out a Roto file with basic
syntax highlighting. (#139) - The return type of registered functions can now be
Optionalor aVerdict.
(#163) - The
FileSpectype is now public in therotocrate. This allows users to
set up their own systems for script discovery and easier testing. (#151) - Roto will now give better error messages when keywords are used as
identifiers. (#157)
Bug fixes
- Fixed the implementation of the
==and!=operators forString. (#141) - Fixed a couple of double frees and memory leaks. We now run the entire test
suite under Valgrind to ensure that we will find these issues sooner.
(#147, #149, #158) - Fixed an issue with the type inference of the return types of
filtermaps.
(#152)
Other changes
v0.5.0
Released 2025-04-23.
Breaking changes
- Renamed
Runtime::basic()toRuntime::new(). - Reading Roto scripts should now be done with a
FileTreeinstead of
roto::read_files. - It is no longer necessary to pass the pointer size to Roto.
- The
roto_method,roto_static_method&roto_functionmacros are more
strict about the types they accept. Previously, some parameters could be
marked as references where they should have been passed by value and vice
versa, leading to incorrect behaviour. - Hyphens are no longer allowed in identifiers (such as names of variables,
functions, types and filtermaps). filter-mapshould now be written asfiltermap- Some outdated constructs were removed and are now treated as parse errors:
rib,table,output-stream.
New
- There is a full module system for Roto now, meaning that scripts can now be
spread out over multiple files. More information can be found in the
documentation. - Optional types have been added. For any type
Tthe typeT?means an
optional value of that type, similar to Rust'sOption<T>. Values of that
type can be constructed withOptional.NoneandOptional.Some(v). It's
also possible to match on optional values.
match optional_u32 {
Some(x) -> x,
None -> 0,
}
- Roto scripts can now contain tests. These are similar to filtermaps
whereacceptmeans a passing test andrejecta failing one.
test this_passes {
accept
}
test this_fails {
reject
}
- The
clifunction in the Roto API can be used to create a simple CLI for
any runtime, allowing for easier debugging and testing of Roto scripts. - The floating-point types
f32andf64were added, including basic
operators and some methods for floating point manipulation.
Bug fixes
- There are several fixes for cloning and dropping of Rust values in Roto
scripts. The implementation is much more sound now and should not lead to
leakage and double frees anymore. - Fixed an issue where one runtime methods would override another method causing
the wrong method to be invoked by a script. - Roto now uses a custom lexer instead of
logosfor more flexibility. This
fixes some small issues around complex literals.