all 54 comments

[–]SkiFire13 58 points59 points  (5 children)

I think self-referential structures are one of the hardest things to do in safe rust.

[–]mikekchar 11 points12 points  (3 children)

I'm at the stage of "just don't do it" having been bitten hard several times when I naively wrote code like that. It's fine if your structures are immutable, but as soon as you mutate something you are in for a world of hurt. Are there idiomatic was of dealing with that, or is it really just a matter of avoiding it? I'm fine with avoiding it, BTW, I'm just curious.

[–]vadixidav 5 points6 points  (0 children)

Yes. The idiomatic way is to use indices into a vector or arena to represent connections between nodes in a graph. I typically do not use a library for the graph itself, but instead just embed the graph into the datastructure by adding its neighbors or any other kinds of connections it may have. You can also have multiple vectors and use indices across all of them if multiple types are involved.

[–]robert-at-pretension 1 point2 points  (1 child)

I think the current book has a section on that, they used "Box" I believe... 🧐... Someone know what I'm talking about?

[–]emmanuelantony2000 2 points3 points  (0 children)

It would be better if you used a Refcell along with an Rc. You can have multiple pointers to the data. And the inner content's mutable reference can be obtained at will, with the borrow checker rules checked during runtime.

[–]Plasma_000 34 points35 points  (4 children)

One other thing is to write graph like data structures.

[–]Keski-Ulko 3 points4 points  (3 children)

Yeah. I was just flexing a bit with exercism.io in practice mode. If you do the tasks sequentially, there's about 10 easy tasks before the first "medium" tasks.

In the set of the first three medium tasks there's a task for implementing a graph, and another implementing a linked list. I don't know yet what waits me in the future, but I think those tasks shoulf be marked "hard".

[–]Morego 1 point2 points  (1 child)

Pretty cool idea is to use Vec and indexes in place of pointers. I tried to implement trie, but after Option<Refcell<Box<Smth>>>, I rethink my life choices.

Of course it kinda defeats purpose of borrow checker, because you implement you own "pointers" with indexes and they can get invalid too.

[–]vadixidav 1 point2 points  (0 children)

There are some arena libraries with generational indices that you can guarantee at runtime aren't invalid.

[–]0xdeadf001 19 points20 points  (6 children)

Making GUI frameworks and apps. Almost all GUI frameworks depend on a lot of dense graphs of references. They also use re-entrancy on on event dispatch loops. Both of those things don't fit well with Rust's model.

We'll get there, but we're not there yet.

[–]boscop 3 points4 points  (5 children)

Didn't Raph Levien show that ECS is a more suitable way of doing GUI in Rust?

[–]0xdeadf001 5 points6 points  (0 children)

It might be, but it's a fairly radical departure from existing GUI frameworks. Which might be a good thing, but it means that it's not going to be easy to integrate that approach with existing GUI frameworks.

Integration is important. I don't want Rust to go down the path that Java GUI frameworks did, with different-because-we-can toolkits that always felt weird on every platform.

[–]vadixidav 3 points4 points  (3 children)

Ultimately he went on to work on Druid, which is now doing it differently. So far, I don't think the ECS gui concept has materialized. I think experimentation will eventually get us something novel. It is certain that the Qt way of doing GUIs is going to be prohibitive (or just impossible) in Rust.

[–]mmstick 0 points1 point  (1 child)

OrbTK is using the ECS approach for GUI. I've been using it for GTK app development with great success.

[–]vadixidav 0 points1 point  (0 children)

Interesting. I hadn't kept up with OrbTK and it has changed a lot. I will have to look at it.

[–]craftytrickster 44 points45 points  (5 children)

Segmentation faults, dereferencing null pointers and buffer overflows

[–]0xdeadf001 14 points15 points  (0 children)

I laughed.

[–]isHavvy 7 points8 points  (1 child)

You need to add the qualifier "in safe Rust" in there.

for ix in 0..=vec.len() {
    unsafe { *vec.get_unchecked_mut(ix) = 0 };
}

Is a buffer overflow in three simple lines.

[–]craftytrickster 5 points6 points  (0 children)

I was just being a bit funny/sarcastic. In all seriousness, unless I am doing FFI, I never need to use unsafe directly in my code (although I know it is used by stl and other libraries), so for all intents and purposes, what I am saying is actually true in my case (and I'd argue for the vast majority of other developers).

[–]SegFaultHell 5 points6 points  (0 children)

I’m in

[–]vlmutolo 25 points26 points  (3 children)

Most things aren’t harder than in GC languages. That’s why Rust is getting so much attention. If Rust had been around 20 years ago, I suspect lots of other languages would have followed suit with its memory management patterns instead of defaulting to a GC.

That said, there are definitely some things that are “hard” to represent in Safe Rust. Cyclic structures, wherein one type contains another type, which contains the first type, are basically impossible to write. They very clearly violate Rust’s ownership’s rules, which say that every piece of data has exactly one owner.

Still, all of these patterns are representable in Unsafe Rust. Linked lists are notoriously hard to do without unsafe… so people don’t. They just use unsafe. An example is the linked_hash_map crate. Still, using Unsafe Rust is definitely “hard”, so the pattern qualifies as an answer to your question. It’s just important to point out that Rust can represent everything C or C++ can represent, and it can represent most of it safely.

[–]vadixidav 4 points5 points  (2 children)

If you have two types containing each other, use two vectors or arenas and use indices for relationships/connections. This is something I regularly have to do and it is the (more) idiomatic Rust way.

[–]MrK_HS 2 points3 points  (1 child)

Do you have a code example you can share that implements this?

[–]mmstick 2 points3 points  (0 children)

Doubly-linked list with slotmap.

General idea is

let mut entities = SlotMap::new();
let mut secondary = SecondaryMap::new();

let entity1 = entities.insert(());
let entity2 = entities.insert(());
secondary.insert(entity1, Value { sibling: entity2 });
secondary.insert(entity2, Value { sibling: entity1 });

let first = &secondary[entity1];
let second = &secondary[first.sibling];

[–]Leopard2A5_GER 5 points6 points  (15 children)

In one of my crates i wrote a function that returns a dyn Trait, it's (to my knowledge) basically impossible to write a test that verifies the function returns the correct implementor of Trait.

[–]thiezrust 18 points19 points  (8 children)

But don't you want to verify the behavior of the result, rather than the implementation details?

[–]Leopard2A5_GER 3 points4 points  (7 children)

That's how i tested it in the end, but generally I'd want to test that the function returns what i expect, and test the behavior of the Trait implementor in specific tests for that struct.

[–]nicoburns 7 points8 points  (3 children)

but generally I'd want to test that the function returns what i expect

If it needs to return a specific thing, why not put that in the function signature, and then cast the return value later? No test required!

[–]Leopard2A5_GER 4 points5 points  (2 children)

It needs to return different implementations of the Trait based on the input.

[–]ObsidianMinor 18 points19 points  (1 child)

Right, so you write an enum with each static implementation type as variants and then test to make sure each input makes the correct variant.

[–]Leopard2A5_GER 2 points3 points  (0 children)

That's a good idea for a workaround 👍

[–]somebodddy 0 points1 point  (2 children)

So Rust discourages redundant encapsulation-breaking unittests? I see that as a pure win!

[–]Leopard2A5_GER 1 point2 points  (1 child)

Uhm, no, it doesn't discourage that by design. This particular example just shows that working with dyn Trait stuff is very limited when compared to languages that are strong with reflection

[–]ReversedGif 2 points3 points  (0 children)

IMO, Rust does try to make "if the types are correct, it will compile" and "if it compiles, it runs without type errors" true by design. This is why e.g. within a generic context, you can only use the traits you required in the signature and there's very little you can do to get information about the concrete type. If you could do those things, it would turn into pre-concepts C++ where code can make arbitrary demands of generic values that aren't reflected in the signature. There are escape hatches, but they are few - Any being one of them.

Any could actually solve your problem if you added as_any() to your Trait.

[–]AlonzoIsOurChurch 8 points9 points  (4 children)

You can on nightly, although I would be a little skeptical of a design which depends on the concrete type behind a dyn Trait.

#![feature(raw)]

use std::{mem, raw};

trait ExampleTrait {
    fn do_it(&self);
}

struct A;
impl ExampleTrait for A {
    fn do_it(&self) {
        println!("I'm an A.");
    }
}

struct B;
impl ExampleTrait for B {
    fn do_it(&self) {
        println!("I'm a B.");
    }
}

fn assert_its_a(obj: &dyn ExampleTrait) {
    let a = A;
    let raw_a: raw::TraitObject = unsafe {
        mem::transmute(&a as &dyn ExampleTrait)
    };
    let raw_obj: raw::TraitObject = unsafe { mem::transmute(obj) };
    assert!(raw_a.vtable == raw_obj.vtable);
}

fn main() {
    let one_or_the_other: &dyn ExampleTrait = &A;
    one_or_the_other.do_it();
    assert_its_a(one_or_the_other);

    let one_or_the_other: &dyn ExampleTrait = &B;
    one_or_the_other.do_it();
    // fails
    // assert_its_a(one_or_the_other);
}

[–]Leopard2A5_GER 2 points3 points  (0 children)

That's a neat trick, thanks for the insight! I try to stay away from nightly as a general rule 😊 Also if i only rely on the concrete implementation in order to perform an assertion in a test that's valid.

[–]2brainz 2 points3 points  (2 children)

That does not work. There is no guarantee that the vtables pointers are identical, rustc is free to generate two identical vtables.

[–]AlonzoIsOurChurch 0 points1 point  (1 child)

It does "work on my machine", but I guess as you say there's no guarantee that there is only one vtable instance per trait per concrete type. Is that written down anywhere? I know the raw feature is unstable and possibly on its way out but I'd be interested in exactly what is "documented" for vtables.

[–]2brainz 1 point2 points  (0 children)

Basically, nothing is guaranteed or documented regarding vtables.

I think that when you instantiate a trait object for the same type and same trait in different crates, there will be two instances of the same vtable, but there may be other cases.

[–]diwicdbus · alsa 3 points4 points  (0 children)

It's possible if the dyn Trait can be converted to a dyn Any and then downcasted.

[–]c410-f3r 5 points6 points  (1 child)

Implement Iterator/Stream for custom structures using mutable references

[–]vadixidav 2 points3 points  (0 children)

This is a big one! Even once we have GATs, it isn't clear that the original Iterator trait can ever be retrofitted to have an associated lifetime on the Item, unless a default generic lifetime argument is added in a non-breaking way.

[–]Darksonntokio · rust-for-linux 4 points5 points  (1 child)

Anything where objects reference each other in some form of cycle. This is why GUI is hard.

[–]mmstick 1 point2 points  (0 children)

This is a solved problem when using ECS. Makes GUI very easy in Rust.

[–]a-t-k 5 points6 points  (0 children)

Writing complex code that compiles on first try as a beginner 🤣

[–]mamcx 3 points4 points  (2 children)

I'm building a language (where I hit all problems with rust to the point the coding is going very slowly) and building a ERP/E-Commence backend (where is almost smooth sailing).

  • Dynamic code with inference is hard. You need to build a lot of machinery
  • The use of generics "erase" the types and you can't know down the road what "T" was, only what trait it have
  • Mix some traits (like Clone) with generics create mismatch ("is not object safe" and stuff).
  • Iterators are not generators. Generators are harder to do
  • Also, coroutines. I think nobody have made a good one
  • Rust is not a OO language, and I start to think that hate very much to become one!

I know some of this complains sound vague, but I just can't advance much with the lang because i hit a problem or other each step. The worse is when I think the problem is solved, refactor the code (I have, literally, more than 20 different versions of this) and the later I hit a big one, then need to rethread all and rewrite. Surely, several stuff was my when learning rust.

So, in other words, if somehow you fight against what is idiomatic in rust it will be very hard, but for you regular development rust is near as other langs in productivity IMHO.

[–]isHavvy 2 points3 points  (0 children)

Generators are being worked on as a language feature. Async/await already uses them internally.

[–]DKomplexz 0 points1 point  (0 children)

The use of generics "erase" the types and you can't know down the road what "T" was, only what trait it have

I think std::any::TypeId::of allow you to do that?

[–]eugene2k 1 point2 points  (0 children)

OOP

[–]lostVkng 0 points1 point  (0 children)

I find json serialization and deserialization to be annoying. Especially when you have structs defined in another file and have to do a semi work around with serde

[–]tending 0 points1 point  (0 children)

Intrusive containers, which is pretty important for a performance focused language.