`Any::downcast_mut` multiple times results in borrow check error

I have this code:

use core::any::Any;

pub enum Either<L, R> {
    Left(L),
    Right(R),
}

impl<'a, L: 'static, R: 'static> Either<&'a mut L, &'a mut R> {
    pub fn downcast_mut_from(from: &'a mut dyn Any) -> Option<Self> {
        if let Some(left) = from.downcast_mut() {
            Some(Self::Left(left))
        } else if let Some(right) = from.downcast_mut() {
            Some(Self::Right(right))
        } else {
            None
        }
    }
}

Rust Playground

which fails with the following borrow check error:

error[E0499]: cannot borrow `*from` as mutable more than once at a time
  --> src/lib.rs:12:37
   |
8  | impl<'a, L: 'static, R: 'static> Either<&'a mut L, &'a mut R> {
   |      -- lifetime `'a` defined here
9  |     pub fn downcast_mut_from(from: &'a mut dyn Any) -> Option<Self> {
10 |         if let Some(left) = from.downcast_mut() {
   |                             ------------------- first mutable borrow occurs here
11 |             Some(Self::Left(left))
   |                  ---------------- argument requires that `*from` is borrowed for `'a`
12 |         } else if let Some(right) = from.downcast_mut() {
   |                                     ^^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here

For more information about this error, try `rustc --explain E0499`.

I can work around this by checking the type with Any::is first and then only calling downcast_mut once, but I'm curious if there's any way to actually be able to call downcast_mut multiple times in this situation?

It's the known shortcoming NLL Problem Case #3, conditional return from a function. You can see some other potential workarounds here (or use the crate).

You could re-call downcast_mut inside the if blocks and unwrap, say, but that doesn't sound better than using Any::is to me.

2 Likes

Since the return type is already Option here, I'd do a .map() to avoid the need for unwrapping:

impl<'a, L: Any, R: Any> Either<&'a mut L, &'a mut R> {
    pub fn downcast_mut_from(from: &'a mut dyn Any) -> Option<Self> {
        if from.is::<L>() {
            from.downcast_mut::<L>().map(Self::Left)
        } else if from.is::<R>() {
            from.downcast_mut::<R>().map(Self::Right)
        } else {
            None
        }
    }
}
3 Likes