2

I have two structs that need references to each other. Due to other reasons these structs need to bypass borrow checker at the same time. So I have a wrapper around *mut T to bypass the checker. Now I am trying to have each struct have a generic type of the second struct.

This is a toy example:

use d2simrs::util::internalref::InternalRef;

pub trait Layer3Trait {
    fn foo() {
        println!("TraitA::foo()");
    }
}

pub trait Layer2Trait {
    fn bar() {
        println!("TraitB::foo()");
    }
}

pub struct SimpleLayer2<Layer3T>
    where Layer3T: Layer3Trait
{
    pub layer3: InternalRef<Layer3T>,
}

pub struct SimpleLayer3<Layer2T>
    where Layer2T: Layer2Trait
{
    pub layer2: InternalRef<Layer2T>,
}

pub type Layer2 = SimpleLayer2<Layer3>;
pub type Layer2Ref = InternalRef<Layer2>;

pub type Layer3 = SimpleLayer3<SimpleLayer2<Layer3>>;
pub type Layer3Ref = InternalRef<Layer3>;

Code for InternalRef is here

For which I get

   |
30 | pub type Layer3 = SimpleLayer3<SimpleLayer2<Layer3>>;
   |                                             ^^^^^^
   |
   = note: ...which again requires computing type of `example2::Layer3`, completing the cycle
note: cycle used when computing type of `example2::Layer2`
  --> examples/network/bypass_borrow/example2.rs:27:32
   |
27 | pub type Layer2 = SimpleLayer2<Layer3>;
   |                                ^^^^^^

Can I somehow re-define something, so that SimpleLayer2 and SimpleLayer3 can have references to one another.

Effie
  • 758
  • 5
  • 15
  • What's the reason behind using traits and cyclic data structures? Neither are common in Rust code. Have you considered `enum`s and [`petgraph`](https://github.com/petgraph/petgraph)? – Lambda Fairy Oct 04 '21 at 11:25
  • @LambdaFairy i am trying to implement something like network protocol stack (it is for a simulator). Layer2 needs to pass messages to layer 3 and layer 3 to layer 2. Here are toy layer 2 and layer 3: https://gist.github.com/effenok/1775e0702184144057d3ff1be93ba5da. The send and receive functions (all of them) are supposed to do something more advanced. Now I am trying to have several implementations of layer 2 and layer 3 and make them a library. And I don't think petgraph will help, because it is a stack inside a single (graph) node. – Effie Oct 04 '21 at 11:40
  • Rather than layer 3 directly calling layer 2 (which creates a cyclic reference), can layer 3 _return_ the messages it will send instead? – Lambda Fairy Oct 04 '21 at 12:28
  • 3
    The reason I'm asking all these questions, instead of answering your immediate concern, is that this looks very unidiomatic. There are many network protocol stack implementations out there, including `smol_tcp` and Fuchsia's `netstack3`, and they all do it without cyclic references. So I would recommend rethinking your approach. – Lambda Fairy Oct 04 '21 at 12:32
  • 1
    i know that it looks very unidiomatic, but my best reference is Omnet++ which is more like java code written is C++ :). The problem with not calling things directly is that when layer3 (its control plane) receives a message, it may need to send more than one message. And wrapping things in vectors every time seams boilerplate enough. I could probably decouple receive so that the outer object calls layers one by one. – Effie Oct 04 '21 at 13:25
  • @LambdaFairy in any case thank you for the references. I think i need them. – Effie Oct 04 '21 at 13:51

1 Answers1

2

Rust does not support cyclic generic types. If you have:

pub type Layer2 = SimpleLayer2<Layer3>;
pub type Layer3 = SimpleLayer3<Layer2>;

Then the concrete type of Layer3 would be:

SimpleLayer3<SimpleLayer2<SimpleLayer3<SimpleLayer2<SimpleLayer3<SimpleLayer2<SimpleLayer3<SimpleLayer2<SimpleLayer3<SimpleLayer2<SimpleLayer3<...>>>>>>>

Which is not allowed.


As nudged at in the comments, it would ideal if your design were heirachial with no cyclic dependencies since it would obviously avoid such problems. But if that is not appropriate, the standard way to solve this would be with Rc/Arcs and trait objects, which would also avoid your internal *mut T wrapper (with RefCell/RwLock if you need mutability):

use std::rc::{Rc, Weak};

pub struct SimpleLayer2 {
    pub layer3: Weak<RefCell<dyn Layer3>>,
}

pub struct SimpleLayer3 {
    pub layer2: Rc<RefCell<dyn Layer2>>,
}

See these other questions for other potential ideas:

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • the solution with rc/refcell does not work, the message call goes layer2.receive -> layer3.receive -> layer2.send, where each one can change the internal state, so it needs more than one mutable reference. this is why i have a separate reference wrapper around pointers. however when i try to do `dyn Layer 2`, then the reference stops compiling because it can't call new on a type whose size is not known at compile time. which i don't understand at all, how can one not know compile time size of a pointer. – Effie Oct 13 '21 at 06:32
  • *"it needs more than one mutable reference"* - that is not allowed in Rust. Using pointers to circumvent the compilers protections to get multiple mutable references would yield *undefined behavior*. Don't do it. Please check out things like `Cell`/`RefCell`/`RwLock`/`Mutex` that provide *interior mutability* through shared references as a safer alternative. – kmdreko Oct 13 '21 at 06:48
  • You'd have to explain more to answer directly. But `dyn Layer2` is an *unsized type* since it represents any potential implementer of the trait. As such you can only meaningfully use it through indirection via reference, pointer, or smart pointer like `Box` or `Rc`. – kmdreko Oct 13 '21 at 06:51
  • thank you for answering so quickly. based on comments, i have reorganized the code, so that there is an external component going the stack up, and then each layer has reference to the layer below and can go down. so i need to get my code back to generate the same error. and the case with pointer is probably a good candidate for a separate question. – Effie Oct 13 '21 at 07:27