Does Rust have a "Feature of Last Resort"?

A “Feature of Last Resort” (FOLR) is a useful feature which solves certain otherwise hard-to-solve problems, but are often best avoided.

I was just reading this article talking about the Features of Last Resort in Go, and now I'm curious... What would we put on Rust's Feature of Last Resort page?

For me, notable mentions would be

2 Likes

Depending on your point of view, anything unsafe may be considered a Rust feature of last resort.

People who are more open to use of unsafe may single out specific bits of it as especially scary, for example transmute[_copy] and static mut are common favorites due to how easily / in how many different ways their use can go wrong.

7 Likes

To me it's generics with multiple layers of abstraction. By that I mean generic functions that don't just take something that implements a trait, but do something more complicated, like wanting a trait that depends on another trait, which is an associated type of something, which is abstract…

If you want me to impl Trait for Type, I can do that no problem. But if a library wants impl<X, Y, Z> Trait for X where Y: <X as Foo>::Something + Z::What it gets fuzzy, spits out unintelligible errors, and I need to draw a diagram to follow how it all connects up together.

// Cool
impl Area for Circle {
    fn area(&self) -> f32
}

// Arrgh
impl<X, Y> Area for X where <AsRef<X> as GeometricFigure>::Shape: Y, Y: Spheroid::TwoDimensional
    fn area<W, Z>(&self) -> W where W: NonNegative<Z>, Z: NumericValueWithFraction
}
4 Likes

I'm just the opposite. I prefer to have these kinds of constraints spelled out.

1 Like

I thought Rust's "feature of last resort" was being able to link with C.

:slight_smile:

9 Likes

Accessing container item by index rather than iterator. :popcorn:

Box gets close. (Not really hard-to-solve rather than often redundant.)

Maybe turbofish need a mention.

Total type erasure and integers as pointers maybe:

struct RuntimeEnvironment {
    memory: HashMap<usize,Rc<dyn Object>>
}
1 Like

In some situations, wrapping everything by Rc<RefCell<T>> or Arc<Mutex<T>> and using .clone() everywhere helps me a lot :smile:

1 Like

I think unsafe fits in well with this definition. It's a useful feature which helps solve hard problems, but is best to avoid when possible.

I think std::mem::transmute() is an extra level of Feature of Last Resort though. 99.9% of the time people use it as a hack around the type system (e.g. because they're trying to do something unsound around lifetimes) and should be shunned... But in that other 0.1% of cases, when you need it, you really need it.

I usually just interpret that as my code telling me to rethink my codebase's story around ownership, and look for alternate architectures :sweat_smile:

Good point! I forgot to mention that little quirk of Rust syntax.

2 Likes

mem::transmute is an interesting one. It's simultaneously one of the most destructively powerful bits of unsafe Rust, and one of the most understood. I realize I'm doing evil things to the type system, but I've gotten pretty comfortable around transmute because it's possible to know exactly what is or isn't sound with it.

I'd say a major feature of last resort (if not the major-est) is jumping to manual allocation with std::alloc. You're giving up basically all type safety and just writing C in the name of manual control.

But I'd fully agree that the entirety of unsafe fits as a "feature of last resort"; safe code is always preferred if it has the same performance characteristics.

If we take a bit of rustjerk, then allocation and copying are features of last resort because performance :stuck_out_tongue:

3 Likes

I see manual allocation and transmutes as somewhat related, in the sense that as soon as you start going back and forth between typed data and bytes (a somewhat common use of transmute and pointer casts), the desire for overaligned allocation is not far away, and AFAIK there is no safe way to do that in Rust.

I assume converting bitwise between floating point and integer types to do stupid stuff with bits (including I/O, hashcodes, or just plain and simple bit-twiddling hacks) falls into the 0.1%.

[off]

The Spheroid::TwoDimensional is my new favorite piece of business speak for "circle".

[/off]

5 Likes

I don't think the Turbofish qualifies, since it is a rather commonly used, well-understood construct that just allows the programmer to put an extra restriction on the type checking to resolve ambiguities.

std::mem::transmute is definitely an interesting one. May I offer its counterpart std::mem::forget as potential 'feature of last resort'? It is fun in that it is not marked as unsafe, since it is fully safe to leak memory. The use cases for doing that however, are very, very limited :smiley: .

3 Likes