-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Newtypes are sometimes tedious, fragile, etc to implement #3
Comments
In languages like C++ or Java with "traditional inheritance", when you want a type that has mostly the same behavior as an existing type, a common solution is to use inheritance to create a subclass implementing the same interface as the base class. Today, this use of inheritance often seems more convenient than Rust's trait system because it delegates all the methods by default. This is part of the reason delegation is expected to improve newtyping. |
One problematic case that comes to mind is when you try to use your newtype in the library that originally declared your wrapped struct. For instance, we have the CSV reader/writer crate, which declares the StringRecord struct. For whatever reason, I would like to implement additional fourth-party traits on it, so I create a newtype - but then when I try to use a function like set_headers, I realize that I can't pass the newtype to it, since it expects the wrapped type explicitly. Even Deref/DerefMut would not help in this case, since the method requires me to give up ownership instead of simply providing a mutable reference, which neither trait can provide. Of course I can simply implement Into and call it, passing the wrapped instance as the result. But this has two issues: the minor one being that it breaks the expectation that, with enough delegation code/other magic, I should be able to use a newtype just like the wrapped type, and the major one being that the more libraries use this method, the uglier it gets. Let's say crate 1 declares struct A. Crate 2 depends on this and creates a newtype from A called B, from which I create a newtype once again to add further traits, called C. Now if I want to use C in a function from crate 1, I need to call "into" twice - I cannot simply get the A field from B since it is most likely private. |
I thought it could be worth adding that the scenario I outlined above could be solved by the inclusion of the "DerefMove" trait, which already has an RFC. This would allow the newtype to be automatically converted to the wrapped type before being consumed. |
Hello all, I wanted to share my use case and the current solution I adopted, which I believe may be integrated with the language. In fact, since I found it so damn tedious to pull it off, I used a regex to apply it, which basically boils down to a meta-compiler :p So, I'm using Pyo3, a crate to create Python-Rust bindings. In this library, the impl std::convert::From<String> for PyErr {
fn from(err: String) -> PyErr {
PyValueError::new_err(err)
}
} Obviously, it leads to an orphan case. Now, to work around this thing for any given method such as the following: fn my_method() -> PyResult<()> {
Err("My error message")
} I have created a regex that applies the following to every damn case and transforms it into: fn my_method() -> PyResult<()> {
Err("My error message").map_err(|err| PyValueError::new_err(err))
} I have actually made a macro for it to repeat a bit less of the code, but the objective result is the one above. This would be the equivalent of creating some Does this make any sense? Am I running in an orphan-rule-driver nightmare loop? Is there a more elegant solution? |
This issue is to gather use cases where the best solution is probably a newtype, but today that newtype is either tedious to implement because many things have to be delegated, fragile to implement because that delegation involves copy-pasting code that should not be copy-pasted, or has some other undesirable code smell.
The text was updated successfully, but these errors were encountered: