lrs-lang / lib

An experimental standard library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

lrs

logo

lrs is a highly experimental, linux-only standard library for the rustc compiler. It does not use any parts of the standard library that is part of the rust distribution.


Features

Since lrs is based on the rust compiler, it shares many features with rust (e.g., lifetimes, borrow checking, integer overflow checking, etc.) But rustc allows us to make significant changes to the language as long as we don’t use Mozilla’s standard library. This section lists some of the differences between rust and lrs and other features of lrs.

Note
In this section we’ll compare programs compiled against lrs and programs compiled against the "standard" standard library that comes with the rust distribution. To make things simpler, we will call programs that use lrs "lrs programs" and programs that use Mozilla’s standard library "rust programs". It should be clear from the context what is meant.

No unwinding

Unwinding and the panic macro have been removed from lrs. This means that error handling works via return values or—​in the case of unrecoverable errors—​process termination. This has the following advantages:

Potentially better performance

Consider the following function:

fn f(a: &mut u8, b: &mut u8, g: fn()) {
    mem::swap(a, b);
    g();
    mem::swap(a, b);
}

If g cannot unwind, then this function can be optimized by removing both swap calls. But if g can unwind, then the swap calls must stay in place since destructors called during unwinding might access a and b.

No exception unsafety

Consider the following (incorrect) rust code:

fn push(a: &mut Vec<T>, g: fn() -> T) {
    unsafe {
        assert!(a.capacity() > a.len());
        let len = a.len();
        a.set_len(len + 1); // <-- BUG
        ptr::write(a.as_mut_ptr().offset(len as isize), g());
    }
}

This is a naive implementation of a non-allocating push method on Vec<T>. The code is incorrect because the length of the vector is increased before the return value of g has been written to it. If g unwinds, the destructor of Vec<T> will access the invalid value at a[len], which is likely undefined behavior. This problem does not exist in lrs. See this (long) thread for a discussion of exception safety in rust.

Small executables

lrs programs usually compile down to executable with a size comparable to that of equivalent C programs.

In the table below, lrs + musl denotes programs that were statically compiled against musl, and lrs - libc denotes programs that don’t depend on a libc.

Name lrs + glibc lrs + musl lrs - libc C (glibc) rust

Hello World

7.0KB

4.0KB

1.3KB

6.5KB

436KB

test

18KB

21KB

n/a

35KB

462KB

A calculator

9.2KB

5.8KB

n/a

n/a

437KB

Note
All programs were compiled with the -O -C lto flags.

Direct system calls

lrs interacts with the kernel directly through system calls. That is, lrs does not depend on a libc for 99% of the work. This allows us to use kernel features that do not (yet) have an equivalent libc function and removes an unnecessary layer of abstraction.

It is, in fact, possible to use lrs without a libc. However, this mode is not yet fully developed and mostly useful for testing cross-compilation.

glibc and musl support

Due to what was discussed in the previous section, the lrs/libc interface is so small that it’s almost trivial to make lrs work with different libc versions. Currently, lrs is known to work with glibc and musl.

Portable

Even though lrs avoids the libc abstraction layer, porting lrs to new linux platforms is easy. This is due to the way the platform dependent parts of lrs mirror the equivalent parts in the linux kernel source code. lrs has already been ported to x86_64, x32, i686, arm, and arm64.

Warning
lrs has only been tested on x86_64.

Per-object allocators

Many allocating structures in lrs (such as vectors, strings, hashmaps) come with an optional allocator argument. The following allocators are part of lrs:

Libc

Uses malloc and friends from the libc.

JeMalloc

Uses jemalloc’s non-standard API with sized allocations and deallocations for higher efficiency.

NoMem

This dummy-allocator always reports an out-of-memory condition.

Bda

The brain-dead allocator only allocates in multiples of the page size. This is very useful for applications that have few allocations whose size is unknown at compile time and can rapidly increase.

Careful note should be taken of the NoMem allocator. Consider the following code:

let mut buf = [0; 20];
let mut vec = Vec::buffered(&mut buf);
write!(&mut vec, "Hello World {}", 10).unwrap();
assert!(&*vec == "Hello World 10");

The vector is backed by the NoMem allocator and the buffer declared in the first line. It will never dynamically allocate any memory. If we were to write more bytes than can be stored in the buffer, write! would return that the vector is out of memory. Using this feature, lrs often allows the user to avoid allocations in cases where doing so would be rather inconvenient in rust.

Nevertheless, it’s easy to use lrs collections in the common case where the user does not care about dynamic allocations. This is because all collections declare a default allocator so that Vec<T> is the same as Vec<T, Heap>. This default allocator can be chosen at compile time.

No allocations in the 99.9% case

All APIs are designed to not allocate memory in the common case. For example, File::open will only allocate memory if the requested path is longer than PATH_MAX. In those cases the API uses the so called fallback allocator. If the user does not want memory to be allocated in those exceptional situations, they can disable said allocator at compile time.

Fast compilation

lrs is split into many small crates and provides incremental compilation independent of the rustc compiler. Compiling a single crate during development often takes less than a second. To this end, lrs comes with its own build system—​lrs_build—​which ensures that only the minimal amount of work is done by the compiler.

Furthermore, even complete builds do not take very long. On this (old) machine, a complete build takes 28 seconds without optimization and 41 seconds with optimization.

Extensive Linux API coverage

lrs already wraps many of the commonly used linux system calls. See this page of the lrs documentation for a list of mostly safe system call wrappers. But note that most of these functions have much nicer wrappers in other parts of the library.

For example, the File struct exposes many syscalls that modify files.

Easy to use

Even though lrs programs don’t use the standard library that comes with the compiler, the user doesn’t have to bother with annoying annotations. For example, the following lrs program can be compiled as written:

use std::tty::{is_a_tty};

fn main() {
    if is_a_tty(&1) {
        println!("stdout is a tty");
    } else {
        println!("stdout is not a tty");
    }
}

This is because lrs comes with its own compiler driver that takes care of injecting lrs instead of rust.

Building and Using

Please see the detailed Building and Using guide.

Examples

See lrs_build for an example of an lrs program. Many more examples can be found in the examples repo. But note that those examples have not yet been organized and documented properly.

Documentation

Documentation regarding lrs in general can be found in the Documentation directory.

Documentation of the library is generated via lrs_doc. An outdated version of said documentation can be found here.

License

The whole library is licensed under the MPL 2.0 license which allows static linking into proprietary programs. It is copy-left on a file-by-file basis: Changes to files licensed under the MPL 2.0 have to be distributed under the same license. It also allows the code to be freely used under several (L)GPL licenses.

Some other parts—​such as the compiler plugin and the compiler driver—​are licensed under the MIT license.

The lrs logo shows a penguin in a sprocket.

It is based on Simple Linux Logo by Dablim and is licensed under CC BY-SA 4.0.

About

An experimental standard library

License:Mozilla Public License 2.0


Languages

Language:Rust 98.5%Language:Makefile 1.1%Language:Assembly 0.2%Language:Shell 0.1%Language:CSS 0.0%