Skip to content

C-DEREF contradicts idiomatic API in the standard library #249

@matklad

Description

@matklad

C-DEREF unambiguously says that Deref is only for smart pointers. However I've noticed (via this tweet) that there's another case where I reach out for Deref. It's not immediately clear to me who is wrong, the API guidelines or my code, so I am raising this issue to figure this out!

EDIT: #249 (comment) gives much better examples

I often implement Deref for newtype struct pattern -- when the struct has a single field, and exists to enforce some static invariant. A good example here is MainfistPath type from rust-analyzer, which is a wrapper around Path which guarantees that parent is not-None. I implement Deref here because ManifestPath is a Path, and I want to get all the methods for free.

Another case is somewhat more obscure, and relates to this code. There, I have a hashbrown hash map of Nodes, but I use the raw API to supply my own, custom Hash. The code currently has a bug where, in one place, default hash impl is used, because Node: Hash. I want to do the following:

struct NoHash<T>(T);
// impl<T> !Hash for NoHash<T> {}

impl<T> Deref for NoHash<T> {}

Again, the reasoning here is that I wrap the type as is, and it would be convenient to get access to all the methods for free.

I suggest focusing on the first case, at it seems more representative to me:

// Current implementation:
pub struct ManifestPath { file: AbsPathBuf }

impl ops::Deref for ManifestPath {
  type Target = AbsPath;

  fn deref(&self) -> &Self::Target { 
    &*self.file 
  }
}

impl ManifestPath {
  // Shadow `parent` from `Deref`.
  pub fn parent(&self) -> &AbsPath {
    self.file.parent().unwrap()
  }
}

// Implementation endorses(?) by the guidelines:

pub struct ManifestPath { file: AbsPathBuf }

impl ManifestPath {
  pub fn file(&self) -> AbsPath { 
    &*self.file 
  }
  pub fn dir(&self) -> &AbsPath {
    self.file.parent().unwrap()
  }
}

Some specific questions:

  • Does the "current version" violate the guideline? To me, it seems that it clearly does, as ManifestPath is not a smart pointer, but my definition of smart pointer might be wrong.
  • What are specific problems caused by the Deref impl? I personally don't see any (not to say that there aren't any)
  • What's the best guideline-compliant way to write ManifestPath?

Quick googling showed one post wich says you can implement Deref for newtypes (cc @JWorthe): https://www.worthe-it.co.za/blog/2020-10-31-newtype-pattern-in-rust.html

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions