Skip to content

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

Open
@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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions