6 comments

  • ekidd1 hour ago
    There&#x27;s a well-known (and frequently encouraged) workaround for the orphan rule: Create a wrapper type.<p>Let&#x27;s say you have one library with:<p><pre><code> pub struct TypeWithSomeSerialization { &#x2F;* public fields here *&#x2F; } </code></pre> And you want to define a custom serialization. In this case, you can write:<p><pre><code> pub struct TypeWithDifferentSerialization(TypeWithSomeSerialization) </code></pre> Then you just implement Serialize and Deserialize for TypeWithDifferentSerialization.<p>This cover most occasional cases where you need to work around the orphan rule. And semantically, it&#x27;s pretty reasonable: If a type <i>behaves</i> differently, then it really isn&#x27;t the same type.<p>The alternative is to have a situation where you have library A define a data type, library B define an interface, and library C implement the interface from B for the type from A. Very few languages actually allow this, because you run into the problem where library D tries to do the same thing library C did, but does it differently. There are workarounds, but they add complexity and confusion, which may not be worth it.
    • dhosek1 hour ago
      The gotcha is what happens when TypeWithSomeSerialization is not something you’re using directly but is contained within SomeOtherTypeWithSomeSerialization which you are using directly. Then things get messy.
  • nixpulvis1 hour ago
    I don&#x27;t think explicit naming of impls is wise. They will regularly be TraitImpl or similar and add no real value. If you want to distinguish traits, perhaps force them to be within separate modules and use mod_a::mod_b::&lt;Trait for Type&gt; syntax.<p>&gt; An interesting outcome of removing coherence and having trait bound parameters is that there becomes a meaningful difference between having a trait bound on an impl or on a struct:<p>This seems unfortunate to me.
  • Animats58 minutes ago
    Note the use case - someone wants to have the ability to replace a base-level crate such as serde.<p>When something near the bottom needs work, should there be a process for fixing it, which is a people problem? Or should there be a mechanism for bypassing it, which is a technical solution to a people problem? This is one of the curses of open source. The first approach means that there will be confrontations which must be resolved. The second means a proliferation of very similar packages.<p>This is part of the life cycle of an open source language. Early on, you don&#x27;t have enough packages to get anything done, and are grateful that someone took the time to code something. Then it becomes clear that the early packages lacked something, and additional packages appear. Over time, you&#x27;re drowning in cruft. In a previous posting, I mentioned ten years of getting a single standard ISO 8601 date parser adopted, instead of six packages with different bugs. Someone else went through the same exercise with Javascript.<p>Go tends to take the first approach, while Python takes the second. One of Go&#x27;s strengths is that most of the core packages are maintained and used internally by Google. So you know they&#x27;ve been well-exercised.<p>Between Github and AI, it&#x27;s all too easy to create minor variants of packages. Plus we now have package supply chain attacks. Curation has thus become more important. At this point in history, it&#x27;s probably good to push towards the first approach.
    • tekacs37 minutes ago
      This is interesting but I wonder if you would accept that this also has the downside of moving at the speed of humans.<p>In a situation where you&#x27;re building, I find the orphan rule frustrating because you can be stuck in a situation where you are unable to help yourself without forking half of the crates in the ecosystem.<p>Looking for improvements upstream, even with the absolute best solutions for option 1, has the fundamental downside that you can&#x27;t unstick yourself.
      • tekacs34 minutes ago
        This is also where I find it surprising that this article doesn&#x27;t mention Scala at all. There are MANY UX&#x2F;DX challenges with the implicit and witness system in Scala, so I would never guess suggest it directly, but never have I felt more enabled to solve my own problems in a language (and yes the absolute most complex, Haskell-in-Scala libraries can absolutely an impediment to this).<p>With AI this pace difference is even more noticeable.<p>I do think that the way that Scala approaches this by using imports historically was quite interesting. Using a use statement to bring a trait definition into scope isn&#x27;t discussed in any of these proposals I think?
  • dathinab1 hour ago
    This isn&#x27;t a new discussion it was there around the early rust days too.<p>And IMHO coherence and orphan rules have majorly contributed to the quality of the eco system.
    • MeetingsBrowser1 hour ago
      can you elaborate on how have they contributed to the quality of the ecosystem?
      • dathinab18 minutes ago
        there is no good way to handle colliding implementation, both from parallel crates and over time<p>without it you can have many many additional forms of breakage, worse you can have &quot;new&quot; breakage between two 3rd party crates without either of them changing due to some impl in a common ancestor changing (e.g. std) and this affecting two wild card implementations in each now leading to an overlap.<p>When you have an overlap there are two options:<p>- fail compilations, but as mentioned this now can happen when std does in non breaking change on two distinct dependencies you have, so not really a good option<p>- try to choose one of them, but that now gets very messy in a) which impl. to choose when and b) the user knowing which is chosen and c) overlap with interactions with stuff like double dispatch, thread local variables, and in general side effects. Issue here are similar to specialization and why that is stuck in limbo, but a magnitude more complex as specialization is only (meant)for optimization while this can be deeply different behavior. Like `foo.bar()` with the same `use Bar as _;` might in one context return an `u32` and in another a `String` ...<p>In many other ecosystems it&#x27;s not uncommon to run into having issues where certain libraries can&#x27;t be used together at all. In rust that is close to not a thing (no_mange collisions are the only exception).<p>Similar, in my experience the likely hood of running into unintended breaking changes is lower in the rust ecosystem then e.g. python or js.<p>Also people are forced to have a somewhat clean dependency tree between crates in ways not all languages requires. This can help with incremental builds and compiler time, a area rust needs any help it can get. (As a side note, clean dependency structures in your modules can (sometimes) help will rust better parallelizing code gen, too.)<p>So overall it forces a good design wrt. handling of some possible categories of breaking changes, mostly eliminating them from rust.<p>Through it can be very annoying. And there is some potential for improvement in many ways.<p>---<p>EDIT: sorry some keyboard fat-fingering somehow submitted that half written response without me pressing enter...
  • nmilo1 hour ago
    I will never stop hating on the orphan rule, a perfect summary of what’s behind a lot of rust decisions. Purism and perfectionism at the cost of making a useful language, no better way to torpedo your ecosystem and make adding dependencies really annoying for no reason. Like not even a —dangerously-disable-the-orphan-rule, just no concessions here.
    • irishcoffee0 minutes ago
      Go: error handling stinks. Generics would be dope.<p>Rust: if you spent 3 weeks understanding the syntax and borrow-checker, here are all of the other problems, and the list keeps growing.<p>Man this cracks me up.
  • grougnax1 hour ago
    If you think Rust has problems, it is that you&#x27;ve have not understood well Rust.