diff --git a/site/content/posts/2023-10-10-is-alphabetic-doc.png b/site/content/posts/2023-10-10-is-alphabetic-doc.png new file mode 100644 index 000000000..7d8826429 Binary files /dev/null and b/site/content/posts/2023-10-10-is-alphabetic-doc.png differ diff --git a/site/content/posts/2023-10-10-my-project-cli.png b/site/content/posts/2023-10-10-my-project-cli.png new file mode 100644 index 000000000..515863324 Binary files /dev/null and b/site/content/posts/2023-10-10-my-project-cli.png differ diff --git a/site/content/posts/2023-10-10-rune-0.13.0.md b/site/content/posts/2023-10-10-rune-0.13.0.md new file mode 100644 index 000000000..176046f5b --- /dev/null +++ b/site/content/posts/2023-10-10-rune-0.13.0.md @@ -0,0 +1,446 @@ ++++ +title = "Rune 0.13" +date = 2023-10-10 +template = "post.html" + +[taxonomies] +categories = ["rust"] +tags = ["tips", "rust"] + +[extra] +author = "John-John Tedro" ++++ + +Welcome to the official release of Rune 0.13! + +Rune is an embeddable dynamic programming language for Rust, it seeks to mimic +the way rust works and is structured. A common way to describe it is to call it +"Rust without types". + + + +For a quick overview: + +* [Modular commandline](#modular-commandline). +* [Code formatting](#code-formatting). +* [Generating documentation](#generating-documentation). +* [Workspace support](#preliminary-workspace-support). +* [Breaking changes to native functions](#breaking-changes-to-native-functions). +* [Memory sandboxing and `rune-alloc``](#memory-sandboxing-and-rune-alloc). +* [`#[no-std]` support](#no-std-support) +* [Migrating](#migrating) +* [Other important changes](#other-important-changes) + +## Modular commandline + +A common problem in Rune is that the CLI we build for it only comes with our own +set of modules. That isn't primarily how rune is supposed to work. You plug in +your own context and use that to build and analyze your scripts. + +With the release of 0.13 we've made the CLI of rune into a module into which you +can build your own CLI with your own context. All you need to do is set up a +Rust project which depends on your project and rune with the `cli` feature +enabled. + +```toml +[package] +name = "my-project-cli" + +[dependencies] +rune = { version = "0.13.0", features = ["cli"] } +my_project = { path = "../my_project" } +``` + +And then you configure and run it like so: + +```rust +const VERSION = "0.13.0"; + +fn main() { + rune::cli::Entry::new() + .about(format_args!("My Rune Project {VERSION}")) + .context(&mut |opts| { + Ok(my_project::setup_rune_context()?) + }) + .run(); +} +``` + +Once you've done this, you can configure the [Rune extension in vscode] to use +this project through cargo rather than the normal rune-cli. + +[](../2023-10-10-my-project-cli.png) + +[Rune extension in vscode]: https://marketplace.visualstudio.com/items?itemName=udoprog.rune-vscode + +## Code formatting + +We have a new CLI subcommand capable of performing code formatting called `rune +fmt`. This can both take individual files and format the workspace: + +``` +== scripts\arrays.rn +++ scripts\async.rn +5 let timeout = time::sleep(time::Duration::from_secs(2)); +6 +7 let result = select { +8 - _ = timeout => Err(Timeout ), + + _ = timeout => Err(Timeout), +9 res = request => res, +10 }?; +11 +``` + +## Generating documentation + +Good documentation is one of these features which is crucial when using a +language for embedding. Without some reference it's hard to know which methods +and types are available for use. + +We strongly believe in the idea that documentation should live close to the code +being documented so Rune now has the ability to generate api documentation from +declared modules. This is aided by the introduction of a few attribute macros +like `#[rune::function]`, `#[rune::macro_]`. + +The following is part of the declaration of our built-in `std::char` module: + +```rust +pub fn module() -> Result { + let mut module = Module::with_crate_item("std", ["char"]); + module.instance_function(is_alphabetic)?; + /* ... */ + Ok(module) +} + +#[inline] +fn is_alphabetic(c: char) -> bool { + char::is_alphabetic(c) +} +``` + +Now we can change it into this, and register the function using `function_meta` +to associate Rust doc comments with the function (documentation borrowed from +Rust): + +```rust +pub fn module() -> Result { + let mut module = Module::with_crate_item("std", ["char"]); + module.function_meta(is_alphabetic)?; + /* ... */ + Ok(module) +} + +/// Returns `true` if this `char` has the `Alphabetic` property. +/// +/// `Alphabetic` is described in Chapter 4 (Character Properties) of the [Unicode Standard] and +/// specified in the [Unicode Character Database][ucd] [`DerivedCoreProperties.txt`]. +/// +/// [Unicode Standard]: https://www.unicode.org/versions/latest/ +/// [ucd]: https://www.unicode.org/reports/tr44/ +/// [`DerivedCoreProperties.txt`]: https://www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt +/// +/// # Examples +/// +/// ```rune +/// assert!('a'.is_alphabetic()); +/// assert!('京'.is_alphabetic()); +/// +/// let c = '💝'; +/// // love is many things, but it is not alphabetic +/// assert!(!c.is_alphabetic()); +/// ``` +#[rune::function(instance)] +#[inline] +fn is_alphabetic(c: char) -> bool { + char::is_alphabetic(c) +} +``` + +Using `rune doc --open` we now get the documentation we expect: + +[](../2023-10-10-is-alphabetic-doc.png) + +Now also if `rune test`, it will pick up and run all documentation tests it can +find: + +``` +#> rune test +... +Test ::std::bytes::Bytes::capacity: ok +Test ::std::bytes::Bytes::clear: ok +Test ::std::bytes::Bytes::reserve: ok +Test ::std::bytes::Bytes::reserve_exact: ok +Test ::std::bytes::Bytes::clone: ok +Test ::std::bytes::Bytes::shrink_to_fit: ok +Test ::std::any::type_name_of_val: ok +Test ::std::any::Type::of_val: ok +Test ::std::any::Type: ok +Executed 412 tests with 0 failures (0 skipped, 0 build errors) in 2.756 seconds +``` + +We now use this on top of our already extensive test suite to test Rune itself. + +If you want to see the documentation that is available for Rune's built-in modules, see: +. + +## Preliminary workspace support + +The CLI can now be told where Rune sources are located using a similar concept +to Rust workspaces. + +This is done by adding a `Rune.toml` in your project, and pointing out any +submodules which might contain other sources: + +```toml +[workspace] +members = [ + "benches", + "examples", +] +``` + +Each project then specifies their own `Rune.toml`, uniquely identifying the component: + +```toml +[package] +name = "rune-benches" +version = "0.0.0" +``` + +For not it's only used to automatically locate tests, benchmarks, examples, and +bins so they can be used with the `rune rune --example ` command. But will +be extended in the future. + +## Breaking changes to native functions + +See [issue #601](https://github.com/rune-rs/rune/pull/601). + +Due to a soundness issues the way native functions are registered has been +changed. + +In Rune we can use references in function calls, but they are not allowed to +outlive the function call from where it originates. Unfortunately due to how +function bindings were built we would accept functions which uses references +that outlives the function call such as `'static` references: + +```rust +fn download(url: &'static str) { + std::thread::spawn(move || { + dbg!(url); + }); +} +``` + +An unfortunate side effect is that the future produced by asynchronous functions +no longer can capture references (even though this is perfectly sound)Ö + +```rust +#[rune::function] +async fn download(url: &str) { + /* .. */ +} +``` + +Such a function instead has to be written using a managed reference like +`Ref` or `Mut` like this: + +```rust +#[rune::function] +async fn download(url: Ref) { + /* .. */ +} +``` + +## Memory sandboxing and `rune-alloc` + +> Sandboxing in general is still **work in progress**. As with everything in +> Rune, it comes without warranty. + +The internal data structures of rune used to, as most Rust programs do, rely on +alloc containers. + +But what if you want to limit the amount of memory a Rune call is allowed to +take? + +One approach might be to install a global allocator which keeps track of the +amount of memory in use. An issue here is that any allocation error from the +perspective of the container is seen as a fatal problem which [will cause the +process to abort]. + +The approach we took with Rune instead is to write our own set of collections. +Or rather fork the ones in `std` and `hashbrown` that we care about. + +This affords us a couple of things which regular containers do not allow for: +* Each operation that might allocate is fallible, so we can simply error instead + of abort the process on allocation errors. +* We can provide raw iterators over these collections that do not take a + lifetime, so that they can be integrated better into the rune iterator + system[^iterator-cloning]. +* We can modify the containers so that they don't require the value being stored + in them to implement a certain set of traits. Or in other words, dynamic + values can be stored in them. + +[will cause the process to abort]: + https://doc.rust-lang.org/std/alloc/fn.handle_alloc_error.html +[^iterator-cloning]: This currently includes many containers need to clone their + entire content as they're being iterated over. + +Now we have basic support for limiting the amount of memory a process is allowed +to use through `rune::alloc::limit`, as long as the `rune-alloc` types are being +used: + +```rust +use rune::alloc::limit; +use rune::alloc::Vec; + +let f = limit::with(1024, || { + let mut vec = Vec::::new(); + + for n in 0..256u32 { + vec.try_push(n)?; + } + + Ok::<_, rune::alloc::Error>(vec.into_iter().sum::()) +}); + +assert!(f.call().is_err()); +``` + +This comes with some caveats, the biggest being that [allocator metadata] is not +taken into account. A clever adversary *might* be able to use this to their +benefit by say performing many small allocations. + +See the [`rune::alloc::limit`] documentation for more information, including +further limitations. + +[allocator metadata]: https://jemalloc.net/jemalloc.3.html#implementation_notes +[`rune::alloc::limit`]: https://docs.rs/rune/latest/rune/alloc/fn.limit.html + +## `#[no-std]` support + +With a bit of effort, you can now use Rune in a no-std environment assuming you +have access to an allocator and a bit of muscle grease. + +See the [no-std-examples] project for how it can be done. + +[no-std-examples]: https://github.com/rune-rs/rune/tree/main/no-std-examples + +## Migrating + +This will be expanded to include information on how to migrate from 0.12.x to +0.13.x as particular pain points are found. + +There is a lot of minor changes, so please bare with us! + +#### The introduction of `VmResult` + +See [issue #478](https://github.com/rune-rs/rune/pull/478). + +Errors in the Rune virtual machine are known as *panics*. In contrast to Rust, +such panics do not cause the whole process to abort, but will only cause the +current virtual machine execution to error. + +Previously this was modelled by having any such fallible functions using +`Result`. Implementing certain traits for such a type is +problematic. For example the `TypeOf` implementation for `Result` should +indicate that it's a dynamic result type, while `Result` should +propagate the type of `T`: + +```rust +impl TypeOf for Result { + fn type_of(&self) -> Type { + /* type of the dynamic Result */ + } +} + +// This implementation should propagate the type of `T`, but conflicts with the above implementation. +impl TypeOf for Result where T: TypeOf { + fn type_of(&self) -> Type { + T::type_of() + } +} +``` + +This makes it impossible to build a blanket implementation that can distinguish +between functions which *panics* by returning a `Result` and results +we want to propagate into Rune. + +To bridge this gap Rune has introduced a special result type called +`VmResult`. This is exclusively used to propagate virtual machine panics and +means that implementations can cleanly distinguish between results and panics. + +Unfortunately this result type can't use the regular try operator (`?`) since +[try_traits_v2 is not yet +stable](https://github.com/rust-lang/rust/issues/84277). To bridge this rune +provides the `vm_try!` macro which behaves like the now deprecated `try!` macro. + +```rust +#[rune::function(instance, path = collect::)] +fn collect_vec(it: Iterator) -> VmResult { + VmResult::Ok(Vec::from(vm_try!(it.collect::()))) +} +``` + +#### Changes to `Any` + +See [#509](https://github.com/rune-rs/rune/pull/509). + +Anything deriving `Any` and is defined inside of a module will need to make use +of `#[rune(item = ..)]` to generate the correct type hash. This is done to +remove a bit of unsafe involving assumptions about `std::any::TypeId`, which +could lead to unsoundness if Rust decides to change its implementation in the +future. + +So this: + +```rust +#[derive(Any)] +struct Process { + /* .. */ +} + +fn install() -> Result { + let mut module = rune::Module::with_crate("process"); + module.ty::()?; + Ok(module) +} +``` + +Will have to have its `#[rune(item = ..)]` setting specified like this: + +```rust +#[derive(Any)] +#[rune(item = ::process)] +struct Process { + /* .. */ +} +``` + +For non-crate modules, the path should be specified without the leading `::`. + +#### Macro attributes takes identifiers instead of strings + +See [#509](https://github.com/rune-rs/rune/pull/509). + +Macros now take paths and identifiers instead of strings, so this: +* `#[rune(name = "Foo")]` is now `#[rune(name = Foo)]`. +* `#[rune(install_with = "path::to::function")]` is now `#[rune(name = + path::to::function)]`. +* `#[rune(module = "rune2")]` is now `#[rune(module = rune2)]`. + +##### Other important changes + +* Integer types have been rename to match their rust equivalents: + * `byte` is now named `u8`. + * `int` is now named `i64`. + * `float` is now named `f64. +* As a result of the above, some module renames have taken place: + * `std::int` module has been renamed `std::i64`. + * `std::float` module has been renamed `std::f64`. +* Literal operators with type hints are now supported: + * `10u8` would correlate to a `byte`, which is now known as `u8`. + * `10i64` would correlate to an `int`, which is now known as a `i64`. + * `10f64` would correlate to a `float`, which is now known as a `f64`. +* Coerce operator ` as ` are now supported, such as: `1u8 as f64`. + Its behavior matches exactly that of Rust but is for now only supported with + the existing built-in types. diff --git a/site/sass/style.scss b/site/sass/style.scss index f9985c1c4..7058a270a 100644 --- a/site/sass/style.scss +++ b/site/sass/style.scss @@ -451,6 +451,12 @@ nav.pagination { border: 1px solid $accent; } +.footnote-definition { + p { + display: inline; + } +} + .rune { margin-top: 10px; margin-bottom: 10px;