How to have a stacked dyn Trait?

I'm dealing with this code, but for simplicity, I'll go with the following code: Rust Playground

fn main() {
    let mut f = match 1 {
        1 => (1..10).into_iter().map(|x| x + 1),
        2 => (1..10).into_iter().map(|x| x + 2),
        _ => (1..10).into_iter().map(|x| x + 3),
    };
    dbg!(f.next());
}

This won't work since closures are distinct unnamed types. So we'll need dyn Trait to continue.
Plain dyn Trait still won't work, since that type should be put behind a pointer type. [1]

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:4:19
  |
3 |       let mut f: &mut dyn Iterator<Item = i32> = match 1 {
  |  ________________________________________________-
4 | |         1 => &mut (1..10).into_iter().map(|x| x),
  | |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
  | |                   |                            |
  | |                   |                            temporary value is freed at the end of this statement
  | |                   creates a temporary value which is freed while still in use
5 | |         2 => &mut (1..10).into_iter().map(|x| x + 1),
6 | |         _ => &mut (1..10).into_iter().map(|x| x + 2),
7 | |     };
  | |_____- borrow later used here
  • second try: owned type via Box Rust Playground , and it definitely works but overkills due to the cost of needless heap allocation.

  • third try: looking for crates that provide stacked allocation functionality, but Miri complains

// stack_dst = "0.7.2"
use stack_dst::ValueA;
fn main() {
    type Dyn = ValueA<dyn Iterator<Item = i32>, [u64; 2]>;
    let mut f = match 1 {
        1 => Dyn::new_stable((1..10).map(|x| x), |p| p as _).unwrap(),
        2 => Dyn::new_stable((1..10).map(|x| x + 1), |p| p as _).unwrap(),
        _ => Dyn::new_stable((1..10).map(|x| x + 2), |p| p as _).unwrap(),
    };
    dbg!(f.next());
}

// works, but is detected as UB by Miri

error: Undefined Behavior: dereferencing pointer failed: 0x27f80[noalloc] is a dangling pointer (it has no provenance)
   --> /root/.cargo/registry/src/rsproxy.cn-0dccff568467c15b/stack_dst-0.7.2/src/value.rs:166:18
    |
166 |         unsafe { &mut *self.as_ptr() }
    |                  ^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: 0x27f80[noalloc] is a dangling pointer (it has no provenance)
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE:
    = note: inside `<stack_dst::ValueA<dyn std::iter::Iterator<Item = i32>, [u64; 2]> as std::ops::DerefMut>::deref_mut` at /root/.cargo/registry/src/rsproxy.cn-0dccff568467c15b/stack_dst-0.7.
2/src/value.rs:166:18: 166:37
    = note: inside `stack_dst::value::trait_impls::<impl std::iter::Iterator for stack_dst::ValueA<dyn std::iter::Iterator<Item = i32>, [u64; 2]>>::next` at /root/.cargo/registry/src/rsproxy.c
n-0dccff568467c15b/stack_dst-0.7.2/src/value/trait_impls.rs:25:9: 25:17
// smallbox = "0.8"
fn main() {
    use smallbox::{smallbox, space::S2, SmallBox};
    type Dyn = SmallBox<dyn Iterator<Item = i32>, S2>;
    let mut f: Dyn = match 1 {
        1 => smallbox!((1..10).map(|x| x)),
        2 => smallbox!((1..10).map(|x| x + 1)),
        _ => smallbox!((1..10).map(|x| x + 2)),
    };
    dbg!(f.next());
}

/// works, but is detected as UB by Miri

error: Undefined Behavior: dereferencing pointer failed: 0x26bd0[noalloc] is a dangling pointer (it has no provenance)
   --> /root/.cargo/registry/src/rsproxy.cn-0dccff568467c15b/smallbox-0.8.1/src/smallbox.rs:368:18
    |
368 |         unsafe { &mut *self.as_mut_ptr() }
    |                  ^^^^^^^^^^^^^^^^^^^^^^^ dereferencing pointer failed: 0x26bd0[noalloc] is a dangling pointer (it has no provenance)
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE:
    = note: inside `<smallbox::SmallBox<dyn std::iter::Iterator<Item = i32>, smallbox::space::S2> as std::ops::DerefMut>::deref_mut` at /root/.cargo/registry/src/rsproxy.cn-0dccff568467c15b/sm
allbox-0.8.1/src/smallbox.rs:368:18: 368:41

  1. Will dyn* Trait, the kinda like owned pointer type, solve this? ↩︎

It's a bit tedious in this case since you have 3 cases, but there is a general trick to make a longer-lived temporary in this kind of situation, and fix your ‘first try’ code: create a variable in the outer scope which is initialized only if it is needed.

fn main() {
    let (mut i1, mut i2, mut i3);
    let f: &mut dyn Iterator<Item = i32> = match 1 {
        1 => { i1 = (1..10).into_iter().map(|x| x + 1); &mut i1 },
        2 => { i2 = (1..10).into_iter().map(|x| x + 2); &mut i2 },
        _ => { i3 = (1..10).into_iter().map(|x| x + 3); &mut i3 },
    };
    dbg!(f.next());
}

This will also work with your complex example.

This trick is also helpful when you have an if/match which wants to sometimes return a borrowed existing value and sometimes a newly created one.

15 Likes

Oh, and another approach, more specific to Iterator::map()-like situations and which may not solve your original problem, is to coerce all the functions to function pointers, so all the arms are identically typed:

fn main() {
    type Fun = fn(i32) -> i32;
    let mut f = match 1 {
        1 => (1..10).into_iter().map((|x| x + 1) as Fun),
        2 => (1..10).into_iter().map((|x| x + 2) as Fun),
        _ => (1..10).into_iter().map((|x| x + 3) as Fun),
    };
    dbg!(f.next());
}

Like dyn Iterator, this also introduces dynamic dispatch, just in the form of function pointers instead of vtable pointers.

11 Likes

Thanks! I'll remember this trick :laughing:

For readers, the unreleased (for a year) stack_dst in github works without UB

// stack_dst = { git = "https://github.com/thepowersgang/stack_dst-rs.git" }
fn main() {
    use stack_dst::{buffers::Ptr2, Value};
    type Dyn = Value<dyn Iterator<Item = i32>, Ptr2>;
    let mut f = match 1 {
        1 => Dyn::new_stable((1..10).map(|x| x), |p| p).unwrap(),
        _ => Dyn::new_stable((1..10).filter(|x| x % 2 == 0), |p| p).unwrap(),
    };
    assert_eq!(f.next(), Some(1));
}

but I still don't recommend it, because some tests in its repo fail under Miri

test retain ... error: Undefined Behavior: attempting a read access using <163381> at alloc58518[0x30], but that tag does not exist in the borrow stack for this location
   --> /rust/tmp/stack_dst-rs/src/fifo.rs:234:30
    |
234 |                     unsafe { ptr::copy(src, dst, words); }
    |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^
    |                              |
    |                              attempting a read access using <163381> at alloc58518[0x30], but that tag does not exist in the borrow stack for this location
    |                              this error occurs as part of an access at alloc58518[0x30..0x40]
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
help: <163381> was created by a SharedReadOnly retag at offsets [0x30..0x80]
   --> /rust/tmp/stack_dst-rs/src/fifo.rs:231:41
    |
231 |                     let src: *const _ = self.data.as_ref()[ofs..].as_ptr();
    |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: <163381> was later invalidated at offsets [0x0..0x80] by a Unique function-entry retag inside this call
   --> /rust/tmp/stack_dst-rs/src/fifo.rs:232:39
    |
232 |                     let dst: *mut _ = self.data.as_mut()[writeback_pos..].as_mut_ptr();
    |                                       ^^^^^^^^^^^^^^^^^^
    = note: BACKTRACE (of the first span):
    = note: inside `stack_dst::Fifo::<dyn std::convert::AsRef<retain::Sentinel>, stack_dst::buffers::ArrayBuf<*const (), stack_dst::buffers::typenum::UInt<stack_dst::buffers::typenum::UInt<stack_dst::buffers::typenum::UInt<stack_dst::buffers::typenum::UInt<stack_dst::buffers::typenum::UInt<stack_dst::buffers::typenum::UTerm, stack_dst::buffers::typenum::B1>, stack_dst::buffers::typenum::B0>, stack_dst::buffers::typenum::B0>, stack_dst::buffers::typenum::B0>, stack_dst::buffers::typenum::B0>>>::retain::<[closure@tests/fifo.rs:70:18: 70:21]>` at /rust/tmp/stack_dst-rs/src/fifo.rs:234:30: 234:56
note: inside `retain`
   --> tests/fifo.rs:70:5
    |
70  |     stack.retain(|v| v.as_ref().0 > 2);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: inside closure
   --> tests/fifo.rs:47:13
    |
46  | #[test]
    | ------- in this procedural macro expansion
47  | fn retain() {
    |             ^

It may not be your answer, but just in case someone finds it helpful, I post this. This whole problem of forcing temporary values to live longer or have forced matching types could be avoided if the example problem is rearranged this way: Rust Playground

Thx. Yeah, if the code can be refactored to aviod dyn Trait, it's the best.

In the original problem, I got

error[E0308]: `match` arms have incompatible types
...
   = note: expected struct `FormatWith<'_, std::slice::Iter<'_, String>, [closure@src/main.rs:22:56: 22:64]>`
              found struct `FormatWith<'_, std::slice::Iter<'_, GenericBound>, [closure@src/main.rs:29:54: 29:62]>`

and FormatWith is used as dyn Display to be written to a string buffer. Note there are two distinct types: the generic parameter in Iter and the closure type in FormatWith, so it's not easy to refator without dyn Display.

I'm satisfied with solution in the accepted answer, though I actually don't use/need dyn Trait when writing more code in that function.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.