rust-lang / rust-lang/rust-clippy

Detect trait object parameters being passed smart pointers which meet the trait object bounds.

Open
#9,589 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-lint
Dominant language
Rust
Stars
13.5k
Forks
2.2k
Avg merge
2d 10h
Merged PRs (30d)
32

Description

What it does

Detect functions which take trait objects and warn if a smart pointer is used as an argument to the function and the smart pointer matches the trait bounds of the trait object.

When implementing internal dynamic dispatch in an API, it may be desirable to have implementations defined using a trait with associated types. However, associated types make it impossible to create a trait object. One solution seen around ecosystem is to blanket implement a "dynamic" trait for all implementations of the other trait. For example:

trait Context: Send + Sync {
    type Value: Send + 'static; // This associated type means Context cannot be a trait object

    fn do_thing(&self, value: &Self::Value);
}

// This trait can be made into a trait object. However the values must be made into a trait object.
trait DynamicContext {
     fn do_thing(&self, value: &(dyn Any + Send));
}

// And here is the blanket implementation.
impl<T> DynamicContext for T
where
    T: Context + 'static,
{
    fn do_thing(&self, value: &(dyn Any + Send)) {
        T::do_thing(value.downcast_ref().unwrap())
    }
}

A public type then may exist to expose the functionality in a type safe way for API users while allowing the implementation to select dynamically how the type should be implemented at runtime:

pub struct Thingy {
    context: Arc<dyn DynamicContext>,
    value: Box<dyn Any + Send>,
}

impl Thingy {
    pub fn do_thing(&self) {
       self.context.do_thing(&self.value)
    }
}

However there is a problem when this code is run. We panic inside of the blanket implementation for do_thing.

This is because a Box<dyn Any + Send> can be upcast to a &(dyn Any + Send). What we actually wanted was the trait object of the inner value of the Box. But instead we got a Box<dyn Any + Send> upcast to the trait object. This can be fixed using as_ref in the case of a Box to access the inner value.

impl Thingy {
    pub fn do_thing(&self) {
        self.context.do_thing(self.value.as_ref())
    }
}

This lint would could also be extended to other smart pointer types.

Lint Name

trait_object_inner_value

Category

suspicious

Advantage
  • Code may assume the trait object passed in can be downcast to a concrete type. This would ensure smart pointers which fail to downcast correctly are warned about when passed as arguments.
Drawbacks
  • Some code may assume that the trait object being upcast should be a Box.
  • Smart pointer types defined outside of std would not work under this lint. It might be possible to detect types which downcast to a trait object but have an inner value that is accessible somehow (via Deref for example) albeit with slightly worse diagnostics. This would not fully work with MutexGuard-like objects though.
Example
struct Thing;

fn downcast_thing(thing: &(dyn Any + Send)) {
    thing.downcast_ref::<Thing>().unwrap()
}

fn with_box(thing_boxed: &Box<dyn Any + Send>) {
    downcast_thing(thing_boxed)
}

Could be written as:

struct Thing;

fn downcast_thing(thing: &(dyn Any + Send)) {
    thing.downcast_ref::<Thing>().unwrap()
}

fn with_box(thing_boxed: &Box<dyn Any + Send>) {
    downcast_thing(thing_boxed.as_ref())
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the issue's Rust examples and the proposed lint name, trait_object_inner_value. Define the supported smart-pointer cases from the Box example and its as_ref correction, then verify that the lint warns on the incorrect argument and accepts the corrected form. No source files or tests are named in the issue, so locating the relevant Clippy lint and test entry points is part of the work.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.