As someone not familiar with Rust, I'm curious what all the symbols there mean. What it's doing is of course obvious, just wondering about the details.
fn is analogous to function in javascript. It takes the form `fn fnname<generics, generics>(argname: argtype) -> ReturnType`
The "weird" 'a here is a generic lifetime. It basically says that the returned value (Term) must not outlive the input values (env and args).
A &[Term<'a>] is a slice of Term<'a>. You can think of a slice as if it was an array whose size is known at runtime.
A Result<Term<'a>, Error> is a type in the standard library used for error handling. It can either be an `Ok(Term)` or an `Err(Error)` depending on whether the function succeeded or not. (There is no special magic here, you can define your own type that can be "either" one or the other. They're called enums in Rust).
> "It basically says that the returned value (Term) must not outlive the input values (env and args)."
It doesn't say that it must not, it says that it doesn't. Because lifetime information can only be inferred about callees and not callers, the lifetime annotation tells the compiler what it needs to know about the caller's environment. It tells the compiler "the lifetimes of these two things are tied together in my caller". The compiler can then use that information when the borrow checker is doing its magic.
I've got an article written in my head about how to think about lifetimes in terms of visibility up and down the call stack.
The same `<'a>` name on all of the inputs and outputs tell the borrow checker that these things are related, i.e. they all borrow from the same thing.
The borrow checker tracks these annotations through all the scopes and function calls back to the "thing" they borrow from, and checks that nothing is borrowed for too long.
I think this particular case is trivial enough that the annotation could be omitted:
but in more complex cases where data from some arguments is returned, and from some it is not, it's useful to annotate which inputs/outputs are related and which aren't, so that the unrelated arguments can be temporary values without tainting everything else as temporary.
The 'a stuff is marking the lifetimes. In this case saying that the return value in the happy case needs the inputs to last at least as long as the return value does before being freed because there might be a dependency between them.
`'a` is a "lifetime parameter"; syntactically, it's like a generic parameter, only it's use to denote how long a given reference stays alive. This is one of the important mechanisms that Rust uses to ensure memory safety without a GC. Every reference in Rust has a lifetime, but many of them can be elided.
`fn add<'a>` just means `define a function with a lifetime parameter called `'a`. This is analogous to if you defined a function as `fn add<T>` with a generic parameter T.
`Env<'a>` means that the type `Env` is parametrized by a lifetime. As before, this is similar to a type being parametrized by a generic parameter. This is usually done when a type contains a reference; Rust requires that references stored in a type must have explicit lifetimes specified. In this case, we're specifying that the lifetime that Env is parametrized over is the lifetime parameter that the function defines. This doesn't mean anything in particular on it's own, but when we use `'a` elsewhere in the signature, it means that the two places use the same lifetime, which is necessary to specify sometimes to clarify to the compiler that something is safe.
`&[Term<'a>]` is a slice of `Term<'a>` values; any type can go between the `[]`, e.g. `&[i32]` for a slice of 32-bit integers. A slice is a view into a sequence of values in memory. One of the things Rust does to aid the programmer in low-level optimizations is define separate types for sequences, namely slices, arrays, and vectors. A slice is a reference to another sequence type (which could be another slice); they're very cheap to make, but cannot be resized. An array is a value type (i.e. not a reference) and must be constant-sized. Finally, a `Vec` is resizable and heap-allocated, so it's expensive to create and copy (relative to slices).
Finally, `Result<Term<'a>, Error>` means that the function returns a `Result` (i.e. either a success or an error); if no error occurs, it will return a `Term<'a>; otherwise, it will return the type `Error` (which is generally custom-defined in a given package, but could be an instance of a standard library error like `std::io::Error`. This is roughly analogous to returning a `Term<'a>` and marking that an exception of type `Error` may be thrown.
One thing to note is that Rust's return values with lifetimes tend be tied to one of the parameters. In this case, the compiler's rules aren't sufficient to infer which of `Env` and `Term` to tie it to, so the programmer needs to disambiguate. The programmer chose to tie them all together, which presumably indicates that the output will reference both parameters in some way (although it's impossible to figure out more precisely how it will from the signature alone).
Lifetimes are definitely the hardest part of Rust in my opinion; the important thing to realize is that changing the lifetime parameters in a function will never change the user-visible semantics of the code. If the lifetimes are invalid, the compiler will error, and if they are valid but not optimal, then the worst case is that that memory might be kept around longer than needed. Unless you're programming something that is super performance critical or will be long running, you generally won't have huge issues if you fix the lifetime compiler errors with trial and error when they come up (which is much less often than you might expect due to the compiler being able to infer them most of the time).
> the important thing to realize is that changing the lifetime parameters in a function will never change the user-visible semantics of the code
To reiterate: the lifetime parameters are used only to check the code for errors, they are discarded after that and don't affect the code generation. There's even an alternative compiler for Rust (mrustc) which completely ignores them and the rest of the borrow checking stuff, assuming the code is correct.