
Rust 1.46 - pietroalbini
https://blog.rust-lang.org/2020/08/27/Rust-1.46.0.html
======
est31
The most exciting component of this release is the const fn improvements. With
loops and if available, you can now do non-trivial computation in const fn for
the first time. It reduces the gap between Rust's const fn and C++'s constexpr
to a large degree. Ultimately, the miri execution engine that this feature
builds upon, supports a much larger set of features that even constexpr
supports. It's been a project spanning years to get it merged into the
compiler, used for const fn evaluation, and stabilize it's features (this part
is far from over).

In addition to the linked examples, I have some code of my own which is made
simpler due to this feature:
[https://github.com/RustAudio/ogg/commit/b79d65dced32342a5f93...](https://github.com/RustAudio/ogg/commit/b79d65dced32342a5f9313960856114d88d50f4a)

Previously, the table was present as an array literal in C-style, now I can
remove it once I decide for the library to require the 1.46 compiler or later
versions.

Link to the old/current generation code:
[https://github.com/RustAudio/ogg/blob/master/examples/crc32-...](https://github.com/RustAudio/ogg/blob/master/examples/crc32-table-
generate.rs)

~~~
marricks
I wonder why && and || are not allowed in const functions?

> All boolean operators except for && and || which are banned since they are
> short-circuiting.

I guess I'm missing something obvious but why does the short circuiting break
const-ness?

~~~
phire
Short circuiting introduces conditional branching. If you call a function on
the right hand side of a || or && it might or might not be executed depending
on the value of the left hand side.

Until this version of rust, all conditional branches were banned from const
functions.

I guess to keep things simple they just banned any feature that might cause
branching.

~~~
marricks
Ahh that makes a lot of sense, if you're going to have a compiler insert the
result of a function having conditional branching seems a bit gnarly I guess?

~~~
kibwen
For the history of this feature request, see [https://github.com/rust-
lang/rust/issues/29608](https://github.com/rust-lang/rust/issues/29608) as a
starting point.

------
sfvisser
I’m learning rust right now and there is a lot to like. Steady updates like
this are also very motivating. The ecosystem feels very sane - especially
compared to npm. Top notch Wasm support, cross compiling is a breeze.

That said, coming from a FP background (mostly Haskell/JS, now TS) Rust is...
hard. I do understand the basic rules of the borrow checker, I do conceptually
understand lifetimes, but actually using them is tricky.

Especially in a combinator world with lots of higher order functions/closures
it’s often completely unclear who should own what. It often feels my
library/dsl code needs to make ownerships decisions that actually depend on
the usage.

Anyways, I guess this gets easier over time, right? Should I avoid using
closures all over the place? Should my code look more like C and less like
Haskell?

 _[edit] great answers all, providing useful context, thanks_

~~~
nemothekid
> _the borrow checker, I do conceptually understand lifetimes, but actually
> using them is tricky._

I've been using Rust for a little over year, almost daily at work, and for
several projects. I have a pretty good intuition about how the borrow checker
works and what needs to be done to appease it. That said, I don't think I'm
any closer to understanding lifetimes. I know conceptually how they are
supposed to work (I need the reference to X to last as long as Y), but anytime
I think I have a situation that could be made better with lifetimes, I can't
seem to get the compiler to understand what I'm trying to do. On top of that
very little of my code, and the code I read actually uses lifetimes.

~~~
jedisct1
I've been writing Rust code since before the 1.0 days, and I still can't
understand lifetimes in practice.

When the compiler starts complaining about lifetimes issues, I tend to make
everything clone()able (either using Rc, or Arc, or Arc+Mutex, or full
clones).

Because if you start introducing explicit lifetimes somewhere, these changes
are going to cascade, and tons of annotations will need to be added to
everything using these types, and their dependent types.

~~~
saagarjha
I think you're really intended to do the latter rather than the former. I
mean, Rust lets you do either–it gives you the choice if performance isn't
your concern–but usually it's better to not clone everything.

~~~
steveklabnik
Nah, either is fine. Rust gives you the tools to do both for good reason.
Which is right for you completely depends.

------
kumarvvr
So, I want to learn Rust. I am a C# / Python programmer, experienced.

Are there any particular set of problems that I can solve systematically, so
that I can learn all the features of Rust?

~~~
datanecdote
I am in similar boat. Python centric data scientist. Very tempted to try to
learn Rust so I can accelerate certain ETL tasks.

Question for Rust experts: On what ETL tasks would you expect Rust to
outperform Numpy, Numba, and Cython? What are the characteristics of a
workload that sees order-of-magnitude speed ups from switching to Rust?

~~~
brundolf
I'm far from an expert, but I would not expect hand-written Rust code to
outperform Numpy. Not because it's Rust and Numpy is written in C, but because
Numpy has been deeply optimized over many years by many people and your custom
code would not have been. When it comes to performance Rust is generally
comparable to C++, as a baseline. It's not going to give you some dramatic
advantage that offsets the code-maturity factor.

Now, if you're doing lots of computation _in Python itself_ \- not within the
confines of Numpy - that's where you might see a significant speed boost.
Again, I don't know precisely how Rust and Cython would compare, but I would
very much expect Rust to be significantly faster, just as I would very much
expect C++ to be significantly faster.

~~~
datanecdote
I deal with a lot of ragged data that is hard to vectorize, and currently
write cython kernels when the inner loops take too long. Sounds like Rust
might be faster than cython? Thanks for the feedback.

~~~
cft
Also it might take 20x less RAM compared to using Python objects like sets and
dicts. In Rust there's no garbage collection, and you can lay out memory by
hand exactly as you want.

------
thomasahle
If anyone wants to know more about const fns, see [https://doc.rust-
lang.org/reference/items/functions.html#con...](https://doc.rust-
lang.org/reference/items/functions.html#const-functions)

It is the Rust way of specifying a function as being _pure_. In other words
the output is dependent only on the function arguments, and not on any
external state.

This means they can be evaluated at compile time. I suppose in the future, it
could also allow better compiler optimizations.

~~~
ko27
Never worked with Rust, but I am pretty sure that having a function be pure is
not enough to evaluate it at compile time.

~~~
mijamo
Why not? By definition the output does not depend on runtime property so you
should be able to compute it at compile time right?

~~~
eloff
Pure functions are functions where the return value only depends on the
function arguments. If the function arguments are not known at compile time,
obviously you can't evaluate it at compile time. It would only be possible to
do that when all the arguments are also known at compile time (constants).

~~~
tgb
But a const fn can also do that: if given non-constant parameters it will be
evaluated at run-time. So I (having never used Rust before) still haven't see
the distinction between pure and const. What's an example of a function that
is pure but cannot be evaluated at compile time with constant parameters?

~~~
eloff
The parent didn't specify calling with constant parameters, which makes a huge
difference. To answer your question, basically anything the compiler doesn't
know how to evaluate - which has been expanded in this release, but does not
include everything still.

~~~
tgb
Looks like we have some terminology confusion. I read mijamo's question as
being about the theoretical ability to evaluate at compile time (the value is
knowable) not whether the compiler does do it, and that's what I meant in my
comment too.

If you say that 'pure' functions are not compile-time-evaluatable because they
may be given parameters that are not known at compile time, then you must also
say that const fns are not compile-time-evaluatable. I think it's also clear
that we mean for const fns to count, so the assumption that the parameters are
known at compile time was implicit in the question.

Under those two assumptions: are pure functions evaluatable (in theory) at
compile-time (on values known at compile time)? As far as I can think, the
answer is yes? In which case, I'm not entirely sure what the distinction
between 'pure' and 'cosnt fn' is supposed to be except to separate out the
part of 'pure' functions that can are evaluated _in practice_. Is there
anything more to it?

~~~
eloff
I think that's it in a nutshell. You can't evaluate everything at compile
time, even when you could theoretically. So you need some way to mark the
subset of pure functions that can be evaluated at compile time, which is what
const fn does. That way if a const fn only calls other const functions you
know you can evaluate it. It's a convenient way of tagging functions.

~~~
thomasahle
That's what I thought I said in the original comment. I wonder what people
disliked.

------
kevinastone
Glad to see `Option::zip` stabilized. I tend to write such a helper in many of
my projects to collect optional variables together when they're coupled.
Really improves the ergonomics doing more railroad-style programming.

~~~
borgel
Do you have an example? I'm having trouble understanding how zip would be used
in practice.

~~~
Arnavion
You sometimes have two Options that must both be Some to have any effect, but
other reasons prevent you from making an Option of a tuple of those two
fields. Eg think of deserializing a JSON that contains optional username and
password strings, but you need both if you are to use them to authenticate to
some remote.

In that case, you currently have to write code like:

    
    
        if let (Some(username), Some(password)) = (username, password) {
            /* both are set */
        }
        else {
            /* at least one is not set */
        }
    

With zip this can be written as `if let Some((username, password)) =
username.zip(password) {` In this case it doesn't look like a big difference,
but it does allow you to chain other Option combinators more easily if you
were doing that instead of writing if-let / match. Using combinators is the
"railroad-style programming" that kevinastone was talking about. For example,
you can more easily write:

    
    
        let (username, password) = username.zip(password).ok_or("one or more required parameters is missing")?;
    

You could of course still do this without .zip(), but it would be clunkier:

    
    
        let (username, password) = username.and_then(|username| password.map(|password| (username, password))).ok_or("one or more required parameters is missing")?;
    

The zip form does lose the information of which of the two original Options
was None, so if you do need that information (say the error message needs to
specify which parameter is missing) you'd still use the non-zip form with a
match.

~~~
Matthias247
The zip solution however requires any reviewer to look up on what it actually
does, whereas the "if let" is more of a language fundamental and known to most
reviewers.

Therefore I would actually prefer the long/verbose form without the zip.

~~~
lmm
It's just the opposite. zip is a normal function written in normal code, so if
the reviewer doesn't know what it does then they can just click through to the
definition in their IDE. Whereas "if let" is some kind of special language
construct that a reviewer has to look up through some special alternative
channel if they don't understand it.

~~~
Matthias247
If I'm doing a code review I don't have an idea. I only see text (yeah -
limitation of the tooling, but reality). I can search for the functions, but
it's a hassle and I want to limit having to do it to as much as possible. It's
even not super easy in Rust, since some functions are defined on (extension)
traits and you don't exactly know where to search for them if you don't have
the IDE support and are not already an expert.

"if let" is certainly a special construct - but it's also one that Rust
authors and reviewers will typically encounter rather fast since a lot of
error handling and Option unwrapping uses it. Knowing the majority of library
functions will imho take longer.

~~~
lmm
Understanding - or looking up - library functions is something you're always
going to have to do during code review (it's well worth getting IDE
integration set up). zip is a very standard and well-known function (I used it
yesterday, in a non-Rust codebase); it may well end up being better-known than
"if let". Learning what a library function does is certainly never harder than
learning what a keyword does and it's often easier (apart from anything else,
you know that a library function follows the normal rules for functions, so if
you can see how its output is used then you can often tell what it does.
Whereas you can't rely on that kind of reasoning with language keywords).

------
LEARAX
The quality of life improvements to cargo look very nice, and I feel that rust
wouldn't be remotely as successful without such a tool. I'm very glad I won't
have to be manually picking target directories out of my borg backups anymore
when I'm running out of disk space.

------
cordite
These const fn’s are cool, but won’t this also lead to long compile times down
the road?

~~~
steveklabnik
Yes, any time you move computation to compile time, it makes the compile time
take longer. As always it is a tradeoff.

One thing that people may not realize, especially now that we have loop. You
may expect this to hang the compiler:

    
    
        const fn forever() -> ! {
            loop {
                
            } 
        }
        
        static FOO: u32 = forever();
    

But it won't:

    
    
        error[E0080]: could not evaluate static initializer
         --> src/lib.rs:2:5
          |
        2 | /     loop {
        3 | |         
        4 | |     } 
          | |     ^
          | |     |
          | |_____exceeded interpreter step limit (see `#[const_eval_limit]`)
          |       inside `forever` at src/lib.rs:2:5
    

This does place an upper limit on any given const fn.

~~~
cordite
That's fantastic!

That could be a real hard-to-source build stall.

------
orthecreedence
Awesome! Any idea when relative links will be available in rustdoc? Seems like
it's just on the edge of stabilizing ([https://github.com/rust-
lang/rust/pull/74430](https://github.com/rust-lang/rust/pull/74430)) but I'm
curious how long it takes to see in a release after this happens.

~~~
steveklabnik
There can be fuzziness here depending on exactly when it lands, but generally,
if something lands in nightly, it'll be in stable two releases after the
current stable.

------
fmakunbound
> if, if let, and match

> while, while let, and loop

> the && and || operators

Common Lisp user here. Why just that? How come you can’t have the entire
language as well as all your language customizations available at compile time
for evaluation?

~~~
pthariensflame
You can! Just not through `const fn`. Rust has macros, which at their limit
are capable of running arbitrary Rust code that manipulates syntax and
communicates with the compilation process, just like in a good old Lisp.

Why isn’t `const fn` like this too? One word answer: determinism. Rust takes
type/memory/access/temporal safety very seriously, and consequentially you
can’t use anything in a `const fn` that isn’t fully deterministic and doesn’t
depend in any way on platform-specific behavior. This includes, for example,
any floating-point computation, or any random number generation, or any form
of I/O, or handling file paths in an OS-specific way, or certain kinds of
memory allocation. The span of things possible in `const fn`s has been
expanding over time, and will in the nearish future largely overtake C++’s
direct counterpart of it (`constexpr` functions) in capability. But some
things will intentionally never be possible in `const fn`s, for the reasons
given above.

------
djhworld
Is there a resource that explains what const functions are and why you would
use them?

~~~
steveklabnik
[https://doc.rust-lang.org/edition-guide/rust-next/const-
fn.h...](https://doc.rust-lang.org/edition-guide/rust-next/const-fn.html)

------
daitangio
Can Mozilla layoffs (on Servo team) impact Rust future? It is just s question
to understand if there are others big rust project healty out of there. Just
curious

~~~
steveklabnik
[https://blog.rust-lang.org/2020/08/18/laying-the-
foundation-...](https://blog.rust-lang.org/2020/08/18/laying-the-foundation-
for-rusts-future.html)

------
echelon
`const fn` improvements are amazing!

I can't wait for when we'll be able to `const fn` all the things. Regex,
expensive constants that feel as though they should be literals, etc.

