
Announcing Rust 1.28 - steveklabnik
https://blog.rust-lang.org/2018/08/02/Rust-1.28.html
======
giovannibajo1
I've been studying Rust on and off in the past 3 months. There are a few
things that I find annoying, they are somehow discussed in the issue tracker,
but it's not clear to me what the priorities are to land them:

* Partial borrow. I can't borrow a field of a structure and then reference another field. I can't cite how many times in a few weeks I've been forced to rethink and split my structures just to work around this, making my code harder to read and write for no good reason than working around this limit of the borrow checker.

* The Rc/RefCell/Box dance is very verbose, when you just want to share a reference to some long-living object within other long-living objects. Worse than verbose, it also causes a runtime overhead because of the RefCell.

* No simple way to declare non-const global objects, like something as easy as a global HashMap of constants strings to constant integers. I'm a little spoiled from Go's excellent support of init functions (both compiler-generated and manually written), but this is something I really miss in Rust. lazy_static! is a workaround which is harder to use and forces the code to be thread-safe even if no threading support is necessary (that is, I can't use my non-thread-safe classes in a lazy_static).

* In generic code, there is no simple way to represent integer types. You need to use the "num" trait which breaks the "as" operator causing big pain and less readable code (as it's normal to covert between integer types).

* No integer generic parameters. We have to use the "typenum" crate which ends up causing less readable code and generates C++-like error messages.

* PhantomData is awkward, it absolutely sounds like something the compiler should handle, not the programmer.

* "Wrapping<u32> \+ 1" doesn't compile, but is there a good reason why it does not? I really wish Rust added wuNN/wiNN native wrapping types, or evolved in a way that I can write the same code with "u32" and "Wrapping<u32>" (another example: "as" doesn't work). Right now, it seems like the solution is half-baked: it tries to use the generic to avoid adding a native type, but the code that users must write is more bloated compared to native types.

~~~
steveklabnik
It can be hard to get a hold on so many features, I hear you. Some of this is
Rust, some of this is your perspective vs. Rust's perspective. Here's what I
have for you:

* Partial borrow.

The status of this is unclear. It does work in many cases. It's complicated. I
would push back a little on the "no good reason" though, but this is gonna be
long so I'll leave it at that.

* Rc/RefCell/Box

Yes, this is painful, but that's because shared ownership is painful. Rust is
pushing you away from a certain kind of architecture. Many people think this
is a good thing...

* No simple way to declare non-const global objects

That is lazy_static. But

> forces the code to be thread-safe even if no threading support is necessary
> (that is, I can't use my non-thread-safe classes in a lazy_static).

This is perceived as a good thing; if you code changes later to use threads,
you're not hosed.

If you want something that's not for multi-threading, then don't use something
that can be used with multiple threading! TLS is probably what you actually
wanted here.

* In generic code, there is no simple way to represent integer types.

We went through three versions of these traits, and were not happy with any of
them. It's a tough problem.

* No integer generic parameters

This is coming; the design work has been accepted, and now it's on to
implementation. It's expected in nightly by the end of the year and probably
stable early next year.

* PhantomData is awkward, it absolutely sounds like something the compiler should handle, not the programmer.

The compiler _could_ try to infer variance, but that means that if your code
changed, you'd have a breaking change. In general, around interface
boundaries, Rust requires you to state what you want.

It's even less awkward than what came before it, for what it's worth.

* "Wrapping<u32> \+ 1" doesn't compile

Rust is pickier about numbers than many langauges; 1u32 + 1u8 doesn't compile
either. Many people find this valuable; you don't have to memorize complex
promotion rules, and you always get the semantic you asked for.

~~~
dikaiosune
Re: non-thread-safe items in lazy_static, you can also wrap them in a mutex
within just the lazy_static so the struct only uses synchronization when it
actually needs to be thread-safe.

~~~
dbaupp
That removes the Sync requirement, but still requires Send, so doesn't
completely resolve the requirement for thread-safety (e.g. Rc can't be
stored).

------
doctor_stopsign
I have to say, the Rust compiler's commitment to nice error messages makes it
a pleasure to work with. It is also cool to hear about the GlobalAllocator
trait stabilization. I'll have to check out what the embedded-Rust space has
been doing in this area!

~~~
k__
Yes, it's nice to see improvemed error messages. I read many people loathed
what the borrow checker threw at them.

~~~
steveklabnik
I'd like to mention that along those lines specifically, a lot of people have
talked about NLL in terms of "the borrow checker accepts more of my code", but
it also produces _way_ better errors. See the "Diagnostics" section of
[http://smallcultfollowing.com/babysteps/blog/2018/06/15/mir-...](http://smallcultfollowing.com/babysteps/blog/2018/06/15/mir-
based-borrow-check-nll-status-update/) for an example that I ran into in real
code. (It's niko's blog but I supplied him with this example)

~~~
k__
I think pouring more time and money into the error messages will pay out in
the long run.

Messages like that make coding low-level stuff fun again.

------
azhenley
A summary of what is new:

\- Global allocators

\- Improved error message for formatting: provides a specific reason why it is
invalid

\- Library stabilizations: NonZero number types

\- Changes to Cargo: src directory can not be modified

~~~
egnehots
Global allocators are nice, but what would be really helpful in some
performance intensive applications is to specify an allocator for a
collection.

~~~
steveklabnik
That work relies on this work; that's coming!

------
pedrocr
After two versions where performance has been unchanged I once again see a
little bit of performance improvement with this version in my pet photo camera
raw decoding benchmark. 1.28 is ~1% faster than 1.27 and nightly seems to be
~1% faster than 1.28 again. Don't know if there have been LLVM updates or if
work on the rust compiler itself has helped. In total, since version 1.17 I'm
measuring almost a 25% improvement in CPU intensive code. I need to make a
more comprehensive benchmark easy to run by everyone (many different kinds of
files, fully automatic running, etc).

~~~
steveklabnik
Due to an upstream LLVM bug, we had to remove some information about aliasing;
that bug was fixed and this was re-enabled in this release. That _may_ be what
you’re seeing.

------
ChrisSD
The NonZero type reminds me of something else I'd like to see in Rust; ranged
integers. By which I mean integers that can only represent a specified range
of numbers.

This is not so much for optimization but for type safety (i.e. avoiding bugs).

~~~
steveklabnik
We'd like that too; it's likely to fall out of const generics, which are
looking likely to hit stable sometime early next year.

~~~
henrikeh
With const generics ranged integers would be implemented as a user specified
check.

How viable would it be to implement an optimization step which seeks to
minimize the number of these checks?

~~~
steveklabnik
I'm not entirely sure what you mean, exactly. As far as I know, all of the
usual optimization passes would already be working to minimize them.

~~~
sanxiyn
Rust could emit LLVM range metadata for ranged integers. IIRC, it already does
so for enum discriminants.

~~~
steveklabnik
That seems quite good!

------
ronjouch
Good times! And to those that like me enjoy a good dead trees-based book to
learn a language, the Rust Book (from NoStarch publishing) arrived yesterday
at my door :)

[https://nostarch.com/Rust](https://nostarch.com/Rust)

~~~
wyldfire
I've been reading the O'Reilly book but I'll look at TRPL next. BTW I'm read
this on Safari: Steve, do you know how publishers/authors get compensated for
Safari books? Are there just normal royalties or is it somehow negotiated in a
bulk rate?

EDIT: Steve answers a semi-related question in [1]

[1]
[https://www.reddit.com/r/rust/comments/8u0ggw/trpl_is_releas...](https://www.reddit.com/r/rust/comments/8u0ggw/trpl_is_releasing_on_amazon_without_customer/e1bwffl/)

~~~
cschmidt
I don't know how far in you are, but I would suggest reading TRPL first. I
read the Programming Rust book first, but I found TRPL a much better
introduction. I wish I had done them in the other order. Maybe read TRPL
first, and then go back to PR.

~~~
artificial
I completely agree and I did the same thing. I own dead tree variants of both
and TRPL approach immensely helped grokking the language whereas the
Programming Rust book feels more like a reference book for dives into
concepts.

------
ainar-g
I wonder, why is it NonZeroU8, NonZeroU16, etc instead of something like
NonZero<u8>, NonZero<u16>, etc?

In theory, in the future, when Rust supports constants as template arguments,
one might even add a const argument, the value that should be used as "null".
E.g. if my u8 never reaches 255, I could use something like Never<u8, 255>.

~~~
asdkhadsj
Can anyone brief what NonZero even is.. or rather, it's application? From the
sounds of it, it's literally just a u8 _(or w /e)_ that can't be zero.. but
what use case does that have?

~~~
steveklabnik
Let's take an enum, like Option:

    
    
        enum Option<T> {
            Some(T),
            None,
        }
    

Under the hood, this is a "tagged union"; Rust has to have enough space for
the T, but also for a tag, to say which variant is the current one.

If T is a NonZero type, then we know that zero is never a valid value. That
means that instead, Rust can use the zero value to mean the None case, and any
other value to be the Some case, completely eliminating the extra tag, and
reducing the size of the Option.

This is a good example of a "zero-cost abstraction"; an Option in this case
has absolutely no extra associated overhead at runtime.

Note that these optimizations aren't specialized for Option; they apply to
anything that has this kind of shape.

~~~
monocasa
Is NonZero* special cased by the language? If for instance, I had a
preexisting binary interface where 0 through (2^n - 2) is valid, but all bits
set is the invalid case, can I explain that to the compiler as well?

~~~
steveklabnik
Yes, it is. The case you're talking about is pertinent to the above discussion
on why it's NonZero* vs NonZero<T>; you cannot currently inform the compiler
in your case.

------
kibwen
Now that users can switch out the global allocator, the next step may be to
change the default allocator for Rust programs from jemalloc (which, note, is
only the default on some platforms) to the system allocator. This has been
blocked on stable global allocator support in order to make sure that users
who find that jemalloc is superior for their program have a recourse to avoid
performance regressions; ideally switching to jemalloc (or tcmalloc, etc.)
should be well-facilitated by Crates.io.

As for the rationale behind moving away from jemalloc as a default, the
tracking issue is here: [https://github.com/rust-
lang/rust/issues/36963](https://github.com/rust-lang/rust/issues/36963)
(TL;DR: lower maintenance burden, easier cross-compilation, smaller binaries,
better system integration (including packaging), and the ability to use
Valgrind).

~~~
sudeepj
In case of FFI, if C & Rust are using the same allocator (say the system
allocator), does it mean that memory allocated by C can be freed by Rust and
vice-versa?

~~~
sanxiyn
Yes.

~~~
dikaiosune
Many types in the standard library do not consider it sound behavior to have
their allocations freed by functions other than their own destructors:

[https://doc.rust-
lang.org/std/boxed/struct.Box.html#method.i...](https://doc.rust-
lang.org/std/boxed/struct.Box.html#method.into_raw)

    
    
        After calling this function, the caller is responsible for the memory 
        previously managed by the Box. In particular, the caller should properly 
        destroy T and release the memory. The proper way to do so is to convert 
        the NonNull<T> pointer into a raw pointer and back into a Box with the 
        Box::from_raw function.
    

I would guess that this might become a fun latent footgun in crates.io code --
everyone writes their unsafe code in a way that Just Works under the default
allocator, but then things break down in confusing ways when enabling
alternative allocators, which should be safe to do even when using external
crates.

~~~
steveklabnik
That's already true in cases; for example, I've seen FFI code that has a use-
after-free bug in it, but because of the different way that the allocator
works on different platforms, on OS X it "worked", but on Linux, it
segfaulted.

------
syn_rst
I'm pretty happy about the GlobalAllocator stabilization. Now you can run
Massif and Heaptrack on rust code without switching to unstable!

------
sbjs
I want to expand my knowledge but don't know whether to invest in learning C++
or Rust. Both look like they have interesting concepts, but Rust is new and
not yet widely used, and I hear C++ is easy to break in confusing ways. (I
have cut Go out of the picture because copying and pasting code is the first
thing you learn not to do in school, but all the Go leaders say you have to do
this since they don't want to implement generics, so I don't trust their
judgment on language design.)

~~~
always_good
If you're trying to expand your knowledge, then waffling over which thing to
learn is just procrastination.

~~~
adwn
No. By that logic, you could as well open a random page on Wikipedia and start
learning new stuff. Choosing to study a certain technology in depth carries
with it a high opportunity cost in the form of your time.

~~~
imtringued
They are almost equivalent choices in terms of return of investment. C++ is
used almost everywhere. Rust will allow you to write programs with less bugs
and similar performance to C++.

The opportunity cost of figuring out which one is better can often be higher
than just trying out both choices. [1]

[1] [https://xkcd.com/1445/](https://xkcd.com/1445/)

~~~
adwn
> _They are almost equivalent choices in terms of return of investment._

Someone who asks whether they should learn C++ or Rust probably doesn't know
this – otherwise, they wouldn't ask.

