Rust

  • 安装
pacman -S rust cargo
rustc -V
cargo -V
pacman -S rustup
rustup install nightly
rustup default nightly
rustup update nightly
rustup self update
rustup set profile minimal
rustup set profile complete
rustup doc --std
rustup target list
rustup target add x86_64-unknown-linux-musl
  • 国内镜像

nano ~/.cargo/config

[source.crates-io]
replace-with = "rustcc"

[source.rustcc]
registry = "https://code.aliyun.com/rustcc/crates.io-index.git"

[source.ustc]
registry = "https://mirrors.ustc.edu.cn/crates.io-index/"
cargo new <project> --bin
cargo check
cargo run
cargo run --example json
cargo build --release
cargo build --release --target x86_64-unknown-linux-musl
cargo rustc --release -- -C target-cpu=skylake
RUSTFLAGS="-C target-cpu=native" cargo build --release
cargo build --features embed_image
cargo update
cargo doc --open
cargo test
cargo test -- --nocapture
cargo bench
CARGO_INCREMENTAL=1 cargo build
rustc +beta --version
cargo +beta build
  • Cross Compile
pacman -S mingw-w64
rustup target add x86_64-pc-windows-gnu
PKG_CONFIG_ALLOW_CROSS=1 cargo build --release --target x86_64-pc-windows-gnu
PKG_CONFIG_ALLOW_CROSS=1 cargo rustc --release --target x86_64-pc-windows-gnu  -- -C link-args=-mwindows
  • Development tools
rustup show
rustup doc
rustup default nightly
rustup update nightly
rustup component add rls-preview rust-analysis rust-src rustfmt-preview
rustup component add rust-docs
cargo fmt
cargo install clippy
cargo install cargo-edit
cargo install cargo-outdated
cargo install cargo-src # exploring code in web browser: cargo src --open
cargo install cargo-watch
cargo install wasm-pack
cargo install --git https://github.com/murarth/rusti
rustup update nightly # update RLS and its dependencies
  • Tools developed using Rust
cargo install ripgrep
cargo install tokei
cargo install loc
cargo install fselect
cargo install exa # ls replacement
cargo install lsd # LSDeluxe, also ls replacement
cargo install bat # cat replacement
cargo install hexyl # hexdump replacement
cargo install dutree # du enhanced
cargo install xsv # a suite of CSV command line utilities
cargo install --git https://github.com/sharkdp/fd
cargo install simple-http-server
cargo install microserver
cargo install rural # curl replacement, HTTPie like API
cargo install jsonpp
  • 样例程序
fn main() {
    // A simple integer calculator:
    // `+` or `-` means add or subtract by 1
    // `*` or `/` means multiply or divide by 2

    let program = "+ + * - /";
    let mut accumulator = 0;

    for token in program.chars() {
        match token {
            '+' => accumulator += 1,
            '-' => accumulator -= 1,
            '*' => accumulator *= 2,
            '/' => accumulator /= 2,
            _ => { /* ignore everything else */ }
        }
    }

    println!("The program \"{}\" calculates the value {}",
              program, accumulator);
}

AOT - Ahead of time compilation JIT - Just in time compilation LTO - Link Time Optimization CTFE - Compile time function execution RAII - Resource Acquisition Is Initialization

DST - Dynamic Sized Type ZST - Zero Sized Type NLL - Non-lexical lifetime HIR - high-level intermediate representation MIR - mid-level intermediate representation

Articles

crates

http://zsck.co/writing/capability-based-apis.html

Rayon splits your data into distinct pieces, gives each piece to a thread to do some kind of computation on it, and finally aggregates results. Its goal is to distribute CPU-intensive tasks onto a thread pool.

Tokio runs tasks which sometimes need to be paused in order to wait for asynchronous events. Handling tons of such tasks is no problem. Its goal is to distribute IO-intensive tasks onto a thread pool.

可以改进的点:

  • 格式化字符串支持嵌入变量名和表达式
  • 可变长数组,用于函数传入不定个数的参数 rfcs#1909
  • trait 名直接用作类型名
  • 一次除法运算,同时获得商和余数
  • 为了避免重叠规则影响代码复用,支持特化 feature(speicialization)
  • 迭代器支持引用类型
  • 关联类型支持生命周期泛型

Notes

  • Goal: memory safety and data-race-free concurrency.
  • Strategy: establishes a clear lifetime for each value.
  • Method: ownership, borrow checker, lifetime parameters, static type system.

If a program has been written so that no possible execution can exhibit undefined behavior, we say that program is well defined. If a language’s type system ensures that every program is well defined, we say that language is type safe.

Rust is both type safe and a systems programming language. Rust does provide for unsafe code, the great majority of programs do not require unsafe code, and Rust programmers generally avoid it, since it must be reviewed with special care.

Three key promises Rust makes about every program that passes its compile-time checks: • No null pointer dereferences. Your program will not crash because you tried to dereference a null pointer. • No dangling pointers. Every value will live as long as it must. Your program will never use a heap-allocated value after it has been freed. • No buffer overruns. Your program will never access elements beyond the end or before the start of an array.

The problem with null pointers is that it’s easy to forget to check for them. Rus simply doesn’t provide a way to introduce null pointers. When we need an optional argument, return value, or so on of some pointer type P, we use Option

.

The Box type is Rust’s simplest form of heap allocation: a Box is an owning pointer to a heap-allocated value of type T. When the box is dropped, the value in the heap it refers to is freed along with it.

When a Rust program indexes an array a with an expression like a[i], the program first checks that i falls within the array’s size n. Sometimes the compiler recognizes that this check can be safely omitted, but when it can’t, Rust generates code to check the array’s index at runtime. If the index is out of bounds, the thread panics.

Rust programs never try to access a heap-allocated value after it has been freed. What is unusual is that Rust does so without resorting to garbage collection or reference counting. Rust has three rules that specify when each value is freed, and ensure all pointers to it are gone by that point. Rust enforces these rules entirely at compile time. • Rule 1: Every value has a single owner at any given time. You can move a value from one owner to another, but when a value’s owner goes away, the value is freed along with it. • Rule 2: You can borrow a reference to a value, so long as the reference doesn’t outlive the value (or equivalently, its owner). Borrowed references are temporary pointers; they allow you to operate on values you don’t own. • Rule 3: You can only modify a value when you have exclusive access to it.

In Rust, for most types (including any type that owns resources like a heap-allocated buffer), assignment moves the value: the destination takes ownership, and the source is no longer considered initialized. Passing arguments to a function and returning values from a function are handled like assignment: they also move such types, rather than copying.

• Some types can be copied bit-for-bit, without the need for any special treatment; Rust permits such types to implement the special trait Copy. Assignment copies Copy types, rather than moving them: the source of the assignment retains its value. • All other types are moved by assignment, never implicitly copied.

Rust permits Copy implementations only for types that qualify: all the values your type comprises must be Copy themselves, and your type must not require custom behavior when it is dropped. The Rust standard library includes a related trait, Clone, for types that can be copied explicitly. Its definition is simple:

pub trait Copy: Clone { }

pub trait Clone {
    fn clone(&self) -> Self;
    fn clone_from(&mut self, source: &Self) { ... }
}

Clone is a supertrait of Copy, so everything which is Copy must also implement Clone. In other words, if something implements Copy, it is also cloneable. Cloning a vector entails cloning each of its elements in turn, so Vec implements Clone whenever T does so; the other container types behave similarly.

When we take a reference to a value, Rust looks at how we use it (Is it passed to function? Stored in a local variable? Saved in a data structure?) and assesses how long it could live. Since these checks are all done at compile time, Rust can’t always be exact, and must sometimes err on the side of caution. Then, it compares the lifetime of the value with the lifetime of the reference: if the reference could possibly outlive the value it points to, Rust complains, and refuses to compile the program. Rust’s borrow checking has improved over time, and will continue to do so.

While a value is borrowed, it musn’t be moved to a new owner. A variable must not go out of scope while it’s borrowed.

References always have lifetimes associated with them; Rust simply lets us omit them when the situation is unambigous.

Rust’s ownership, moves, and borrows end up being a reasonably pleasant way to express resource management.

How Rust innovates the greatest is by statically tracking ownership and lifetimes of all variables and their references. The ownership system enables Rust to automatically deallocate and run destructors on all values immediately when they go out of scope, and prevents values from being accessed after they are destroyed. It is what makes Rust memory safe and thread safe, and why it doesn't need a GC to accomplish that.

Rust, through ownership, effectively enforces the higher-level single responsibility principle that sometimes is a struggle to be consistent about in other languages. That alone is amazing.

It does make hard things easy, but only after you've fully embraced Rust.

If you want to learn a language you don't know already, one very educational way to do it is to rewrite some piece of software you already understand.

A crate is a unit of compilation and linking, as well as versioning, distribution and runtime loading. A crate contains a tree of nested module scopes. The top level of this tree is a module that is anonymous and any item within a crate has a canonical module path denoting its location within the crate's module tree.

A Rust source file describes a module, the name and location of which — in the module tree of the current crate — are defined from outside the source file: either by an explicit mod item in a referencing source file, or by the name of the crate itself. Every source file is a module, but not every module needs its own source file: module definitions can be nested within one file.

Each source file contains a sequence of zero or more item definitions, and may optionally begin with any number of attributes that apply to the containing module, most of which influence the behavior of the compiler. The anonymous crate module can have additional attributes that apply to the crate as a whole.

A crate that contains a main function can be compiled to an executable. If a main function is present, its return type must be () ("unit") and it must take no arguments.

A module is a container for zero or more items. A module without a body is loaded from an external file. When a nested submodule is loaded from an external file, it is loaded from a subdirectory path that mirrors the module hierarchy. The directories and files used for loading external file modules can be influenced with the path attribute.

Items are organized within a crate by a nested set of modules. Every crate has a single "outermost" anonymous module; all further items within the crate have paths within the module tree of the crate.

There are several kinds of item:

  • extern crate declarations
  • use declarations
  • modules
  • functions
  • type definitions
  • structs
  • enumerations
  • constant items
  • static items
  • traits
  • implementations

All items except modules, constants and statics may be parameterized by type. The type parameters of an item are considered "part of the name", not part of the type of the item.

mod foo; is basically a way of saying “look for foo.rs or foo/mod.rs and make a module named foo with its contents”. It’s the same as mod foo { ... } except the contents are in a different file.

use blash create an item named blah “within the module”, which is basically something like a symbolic link, paths are relative to the root module. pub use says “make this symlink, but let others see it too”. use self::foo means “find me foo within the current module”.

Usually a use declaration is used to shorten the path required to refer to a module item. These declarations may appear in modules and blocks, usually at the top.

use p::q::r as x;
use a::b::{c,d,e,f};
use a::b::{self, c, d}; // use a::b; use a::b::{c,d};
use a::b::*;

The paths contained in use items are relative to the crate root. It is also possible to use self and super at the beginning of a use item to refer to the current and direct parent modules respectively.

A function item defines a sequence of statements and a final expression, along with a name and a set of parameters. Other than a name, all these are optional. A generic function allows one or more parameterized types to appear in its signature.

Trait bounds can be specified for type parameters to allow methods with that trait to be called on values of that type. This is specified using the where syntax:

use std::fmt::Debug;

fn foo<T>(x: &[T]) where T: Debug {
    // details elided
}

foo(&[1, 2]);

When a generic function is referenced, its type is instantiated based on the context of the reference. The type parameters can also be explicitly supplied in a trailing path component after the function name. This might be necessary if there is not sufficient context to determine the type parameters. For example, mem::size_of::<u32>() == 4.

A type alias defines a new name for an existing type.

type Point = (u8, u8);
let p: Point = (41, 68);

A unit-like struct is a struct without any fields, defined by leaving off the list of fields entirely. Such a struct implicitly defines a constant of its type with the same name. A tuple struct is a nominal tuple type, also defined with the keyword struct.

tuple 类型没有自己的名字,每个成员也没有自己的名字; tuple struct 类型有自己的名字,但是每个成员没有自己的名字; struct 类型有自己的名字,而且每个成员也有自己的名字。

An enumeration is a simultaneous definition of a nominal enumerated type as well as a set of constructors, that can be used to create or pattern-match values of the corresponding enumerated type. Enumeration constructors can have either named or unnamed fields:

enum Animal {
    Rat,
    Dog (String, f64),
    Cat { name: String, weight: f64 },
}

let mut a: Animal = Animal::Dog("Cocoa".to_string(), 37.2);
a = Animal::Cat { name: "Spotty".to_string(), weight: 2.7 };

Each enum value has a discriminant which is an integer associated to it if none of the variants have data attached.

A constant item is a named constant value which is not associated with a specific memory location in the program. Constants are essentially inlined wherever they are used, meaning that they are copied directly into the relevant context when used. References to the same constant are not necessarily guaranteed to refer to the same memory address.

A static item is similar to a constant, except that it represents a precise memory location in the program. A static is never "inlined" at the usage site, and all references to it refer to the same memory location. Static items have the static lifetime, which outlives all other lifetimes in a Rust program. Static items may be placed in read-only memory if they do not contain any interior mutability.

If a static item is declared with the mut keyword, then it is allowed to be modified by the program. An unsafe block is required when either reading or writing a mutable static variable. Mutable statics have the same restrictions as normal statics, except that the type of the value is not required to ascribe to Sync.

A trait describes an abstract interface that types can implement. This interface consists of associated items, which come in three varieties:

  • functions
  • constants
  • types

All traits define an implicit type parameter Self that refers to "the type that is implementing this interface". Traits may also contain additional type parameters. Traits can include default implementations of methods, as in:

trait Foo {
    fn bar(&self);
    fn baz(&self) { println!("We called baz."); }
}

Trait methods may be static, which means that they lack a self argument.

trait Num {
    fn from_i32(n: i32) -> Self;
}
impl Num for f64 {
    fn from_i32(n: i32) -> f64 { n as f64 }
}
let x: f64 = Num::from_i32(42);

Type parameters can be specified for a trait to make it generic.

trait Seq<T> {
    fn len(&self) -> u32;
    fn elt_at(&self, n: u32) -> T;
    fn iter<F>(&self, F) where F: Fn(T);
}

It is also possible to define associated types for a trait.

trait Container {
    type E;
    fn empty() -> Self;
    fn insert(&mut self, Self::E);
}

Traits may inherit from other traits. Multiple supertraits are separated by +, trait Circle : Shape + PartialEq { }. Traits also define a trait object with the same name as the trait. Values of this type are created by coercing from a pointer of some specific type to a pointer of trait type.

An implementation is an item that implements a trait for a specific type. Implementations are defined with the keyword impl. All methods declared as part of the trait must be implemented, with matching types and type parameter counts.

struct Circle {
    radius: f64,
    center: Point,
}

impl Copy for Circle {}

impl Clone for Circle {
    fn clone(&self) -> Circle { *self }
}

It is possible to define an implementation without referring to a trait. The methods in such an implementation can only be used as direct calls on the values of the type that the implementation targets. Such implementations are limited to nominal types (enums, structs, trait objects), and the implementation must appear in the same crate as the self type:

struct Point {x: i32, y: i32}

impl Point {
    fn log(&self) {
        println!("Point is at ({}, {})", self.x, self.y);
    }
}

let my_point = Point {x: 10, y:11};
my_point.log();

An implementation can take type parameters, which can be different from the type parameters taken by the trait it implements.

impl<T> Seq<T> for Vec<T> {
    /* ... */
}
impl Seq<bool> for u32 {
    /* Treat the integer as a sequence of bits */
}

External blocks form the basis for Rust's foreign function interface. Declarations in an external block describe symbols in external, non-Rust libraries.

By default, everything in Rust is private, with one exception. Enum variants in a pub enum are also public by default. When an item is declared as pub, it can be thought of as being accessible to the outside world. If an item is public, then it can be used externally through any of its public ancestors. If an item is private, it may be accessed by the current module and its descendants.

Rust allows publicly re-exporting items through a pub use directive. It essentially allows public access into the re-exported item.

// Any external crate referencing implementation::api::f would receive a privacy violation, while the path api::f would be allowed.
pub use self::implementation::api;

mod implementation {
    pub mod api {
        pub fn f() {}
    }
}

Any item declaration may have an attribute applied to it. An attribute is a general, free-form metadatum that is interpreted according to name, convention, and language and compiler version. Attributes may appear as any of:

  • A single identifier, the attribute name
  • An identifier followed by the equals sign '=' and a literal, providing a key/value pair
  • An identifier followed by a parenthesized list of sub-attribute arguments

Attributes with a bang ("!") after the hash ("#") apply to the item that the attribute is declared within. Attributes that do not have a bang after the hash apply to the item that follows the attribute.

An example of attributes:

// General metadata applied to the enclosing module or crate.
#![crate_type = "lib"]

// A function marked as a unit test
#[test]
fn test_foo() {
    /* ... */
}

// A conditionally-compiled module
#[cfg(target_os="linux")]
mod bar {
    /* ... */
}

// A lint attribute used to suppress a warning/error
#[allow(non_camel_case_types)]
type int8_t = i8;

The derive attribute allows certain traits to be automatically implemented for data structures.

#[derive(PartialEq, Clone)]
struct Foo<T> {
    a: i32,
    b: T,
}

The generated impl for PartialEq is equivalent to

impl<T: PartialEq> PartialEq for Foo<T> {
    fn eq(&self, other: &Foo<T>) -> bool {
        self.a == other.a && self.b == other.b
    }

    fn ne(&self, other: &Foo<T>) -> bool {
        self.a != other.a || self.b != other.b
    }
}

Rust is primarily an expression language. This means that most forms of value-producing or effect-causing evaluation are directed by the uniform syntax category of expressions. A statement is a component of a block. A block is a component of an outer expression or function. A declaration statement is one that introduces one or more names into the enclosing statement block. The declared names may denote new variables or new items. An expression statement is one that evaluates an expression and ignores its result.

A let statement introduces a new set of variables, given by a pattern. The pattern may be followed by a type annotation, and/or an initializer expression. When no type annotation is given, the compiler will infer the type, or signal an error if insufficient type information is available for definite inference.

An expression evaluates to a value, and may has effects during evaluation. The structure of expressions dictates the structure of execution. Blocks are just another kind of expression, so blocks, statements, expressions, and blocks again can recursively nest inside each other to an arbitrary depth.

Expressions are divided into two main categories: lvalues and rvalues. Likewise within each expression, sub-expressions may occur in lvalue context or rvalue context. The evaluation of an expression depends both on its own category and the context it occurs within.

An lvalue is an expression that represents a memory location. These expressions are paths (which refer to local variables, function and method arguments, or static variables), dereferences (*expr), indexing expressions (expr[expr]), and field references (expr.f). All other expressions are rvalues.

The left operand of an assignment or compound-assignment expression is an lvalue context, as is the single operand of a unary borrow. The discriminant or subject of a match expression may be an lvalue context, if ref bindings are made, but is otherwise an rvalue context. All other expression contexts are rvalue contexts.

When an lvalue is evaluated in an lvalue context, it denotes a memory location; when evaluated in an rvalue context, it denotes the value held in that memory location.

When an rvalue is used in an lvalue context, a temporary un-named lvalue is created and used instead. The lifetime of temporary values is typically the innermost enclosing statement; the tail expression of a block is considered part of the statement that encloses the block. When a temporary rvalue is being created that is assigned into a let declaration, however, the temporary is created with the lifetime of the enclosing block instead.

When a local variable is used as an rvalue, the variable will be copied if its type implements Copy. All others are moved.

A literal expression consists of one of the literal forms. A path used as an expression context denotes either a local variable or an item. Tuple expressions are written by enclosing zero or more comma-separated expressions in parentheses. An array expression is written by enclosing zero or more comma-separated expressions of uniform type in square brackets. A struct expression forms a new value of the named struct type. Note that for a given unit-like struct type, this will always be the same value. A block expression is similar to a module in terms of the declarations that are possible. Each block conceptually introduces a new namespace scope.

A block will execute each statement sequentially, and then execute the expression (if given). If the block ends in a statement, its value is ():

let x: () = { println!("Hello."); };

If it ends in an expression, its value and type are that of the expression:

let x: i32 = { println!("Hello."); 5 };
assert_eq!(5, x);

A field expression denotes a field of a struct, consists of an expression followed by a single dot and an identifier. A method call consists of an expression followed by a single dot, an identifier, and a parenthesized expression-list. Method calls are resolved to methods on specific traits, either statically dispatching to a method if the exact self-type of the left-hand-side is known, or dynamically dispatching if the left-hand-side expression is an indirect trait object.

Array-typed expressions can be indexed by writing a square-bracket-enclosed expression (the index) after them. Indices are zero-based, and may be of any integral type. Vector access is bounds-checked at compile-time for constant arrays being accessed with a constant index value. Otherwise a check will be performed at run-time that will put the thread in a panicked state if it fails.

The .. operator will construct an object of one of the std::ops::Range variants.

1..2;   // std::ops::Range
3..;    // std::ops::RangeFrom
..4;    // std::ops::RangeTo
..;     // std::ops::RangeFull

Similarly, the ... operator will construct an object of one of the std::ops::RangeInclusive variants.

MIR

MIR stands for mid-level IR, because the MIR comes between the existing HIR (“high-level IR”, roughly an abstract syntax tree) and LLVM (the “low-level” IR).

At the highest level MIR describes the execution of a single function. It consists of a series of declarations regarding the stack storage that will be required and then a set of basic blocks.

MIR = fn({TYPE}) -> TYPE {
    {let [mut] B: TYPE;}  // user-declared bindings and their types
    {let TEMP: TYPE;}     // compiler-introduced temporary
    {BASIC_BLOCK}         // control-flow graph
};

The storage declarations are broken into two categories:

  • User-declared bindings have a 1-to-1 relationship with the variables specified in the program.
  • Temporaries are introduced by the compiler in various cases.

Each basic block has an id and consists of a sequence of statements and a terminator.

BASIC_BLOCK = BB: {STATEMENT} TERMINATOR

A STATEMENT can have one of three forms:

STATEMENT = LVALUE "=" RVALUE                 // assign rvalue into lvalue
          | SetDiscriminant(Lvalue, Variant)  // Write the discriminant for a variant to the enum Lvalue
          | StorageLive(Lvalue)   // Start a live range for the storage of the local
          | StorageDead(Lvalue)   // End the current live range for the storage of the local

The TERMINATOR for a basic block describes how it connects to subsequent blocks:

TERMINATOR = GOTO(BB)              // normal control-flow
           | IF(LVALUE, BB0, BB1)  // test LVALUE and branch to BB0 if true, else BB1
           | SWITCH(LVALUE, BB...) // load discriminant from LVALUE (which must be an enum),
                                   // and branch to BB... depending on which variant it is
           | CALL(LVALUE0 = LVALUE1(LVALUE2...), BB0, BB1)
                                   // call LVALUE1 with LVALUE2... as arguments. Write
                                   // result into LVALUE0. Branch to BB0 if it returns
                                   // normally, BB1 if it is unwinding.
           | Return                // return to caller normally
           | Drop                  // Drop the Lvalue
           | DropAndReplace        // Drop the Lvalue and assign the new value over it
           | Assert                // Jump to the target if the condition has the expected value,  otherwise panic with a message and a cleanup target.
           | Resume                // the landing pad is finished and unwinding should continue
           | Unreachable           // Indicates a terminator that can never be reached

An LVALUE represents a path to a memory location. This is the basic "unit" analyzed by the borrow checker.

LVALUE = B                   // reference to a user-declared binding
       | TEMP                // a temporary introduced by the compiler
       | ARG                 // a formal argument of the fn
       | STATIC              // a reference to a static or static mut
       | RETURN              // the return pointer of the fn
       | LVALUE.f            // project a field or tuple field, like x.f or x.0
       | *LVALUE             // dereference a pointer
       | LVALUE[LVALUE]      // index into an array (see disc. below about bounds checks)
       | (LVALUE as VARIANT) // downcast to a specific variant of an enum

An RVALUE represents a computation that yields a result. This result must be stored in memory somewhere to be accessible.

RVALUE = Use(LVALUE)                // just read an lvalue
       | [LVALUE; LVALUE]
       | &'REGION LVALUE
       | &'REGION mut LVALUE
       | LVALUE as TYPE
       | LVALUE <BINOP> LVALUE
       | <UNOP> LVALUE
       | Struct { f: LVALUE0, ... } // aggregates, see section below
       | (LVALUE...LVALUE)
       | [LVALUE...LVALUE]
       | CONSTANT
       | LEN(LVALUE)                // load length from a slice, see section below
       | BOX                        // malloc for builtin box, see section below
BINOP = + | - | * | / | ...         // excluding && and ||
UNOP = ! | -                        // note: no `*`, as that is part of LVALUE

We could also expand the scope of operands to include both lvalues and some simple rvalues like constants. Constants are a subset of rvalues that can be evaluated at compilation time:

CONSTANT = INT
         | UINT
         | FLOAT
         | BOOL
         | BYTES
         | STATIC_STRING
         | ITEM<SUBSTS>                 // reference to an item or constant etc
         | <P0 as TRAIT<P1...Pn>>       // projection
         | CONSTANT(CONSTANT...)        //
         | CAST(CONSTANT, TY)           // foo as bar
         | Struct { (f: CONSTANT)... }  // aggregates...
         | (CONSTANT...)                //
         | [CONSTANT...]                //

Nighly rust

what is suggested a generic closure should desugar to.

#![feature(unboxed_closures)]
#![feature(fn_traits)]

use std::ops::{FnOnce, FnMut, Fn};

struct Id;

impl<T> FnOnce<(T,)> for Id {
    type Output = T;
    extern "rust-call" fn call_once(self, (t,): (T,)) -> T { t }
}

impl<T> FnMut<(T,)> for Id {
    extern "rust-call" fn call_mut(&mut self, (t,): (T,)) -> T { t }
}

impl<T> Fn<(T,)> for Id {
    extern "rust-call" fn call(&self, (t,): (T,)) -> T { t }
}

fn main() {
    let id = Id;
    println!("{}", id(4));
    println!("{}", id(true));
    println!("{}", id("hello"));
}

Immutable data structures are inherently tree-like. It is a pain to work with graph-like structures and you have to give up some guarantees the languages give you.

results matching ""

    No results matching ""