[HN Gopher] Rust Any part 3: we have upcasts
       ___________________________________________________________________
        
       Rust Any part 3: we have upcasts
        
       Author : jmillikin
       Score  : 154 points
       Date   : 2025-03-30 11:15 UTC (11 hours ago)
        
 (HTM) web link (lucumr.pocoo.org)
 (TXT) w3m dump (lucumr.pocoo.org)
        
       | jtrueb wrote:
       | Getting closer and closer to OOP
        
         | diggan wrote:
         | At least they're not expanding the standard library for reading
         | emails, yet...
        
           | the_mitsuhiko wrote:
           | I'm a bit confused by both this comment and the previous one.
           | Fundamentally nothing new is unlocked by this, that wasn't
           | already possible for many years. It's just the ergonomics
           | that got much better through this change.
        
             | BerislavLopac wrote:
             | The second comment refers to Zawinski's Law [0].
             | 
             | [0]
             | https://en.wikipedia.org/wiki/Jamie_Zawinski#Zawinski's_Law
        
               | tialaramex wrote:
               | I can't speak for your parent but I'm aware of Zawinski's
               | Law and I could see that's what the comment was about,
               | but like your parent it's not at all clear to me why,
               | it's a non-sequitur - this is giving Rust a convenient
               | safe way to up cast, that's not anywhere in the ballpark
               | of the sort of "expand to do everything" that Jamie was
               | describing as inevitable.
               | 
               | If you say "Ooh, the new kitten is learning how the cat
               | flap works" and I respond "Those who cannot remember the
               | past are condemned to repeat it" and then you respond
               | with confusion that's because my quoting of Santayana is
               | a non-sequitur. I might even agree with his sentiment
               | about the importance of history, but the kitten learning
               | how to use a cat flap isn't anywhere close to what
               | Santayana was talking about so...
        
               | diggan wrote:
               | My jest wasn't meant to say that Rust is expanding to do
               | everything, but rather the opposite. The comment I
               | replied to somehow seems to believe Rust is becoming more
               | "OOP", so I took it a step further and also referenced a
               | fairly known pitfall for platforms (so doesn't even apply
               | to programming languages, in my mind).
               | 
               | In the end, it's a joke with no even a pinch of truth,
               | which seems to have landed flat, that's on me I suppose.
        
               | capitol_ wrote:
               | Since rust is popular it naturally attracts a counter
               | movement, so some people will grasp at straws in order to
               | say something negative.
        
         | GolDDranks wrote:
         | Just having features associated with OOP isn't a bad thing.
         | Object upcasting has its uses.
         | 
         | It's some of the other stuff that gets OOP its bad rap.
         | 
         | Garbage collection, common in many OOP languages, enables
         | having arbitrary object graphs. The programmer doesn't need to
         | think hard about who has a reference to who, and how long these
         | references live. This leads to performance-defeating pointer
         | chasing, and fuzzy, not-well-thought-of object lifetimes, which
         | in turn lead to accidental memory leaks.
         | 
         | Additionally, references in major languages are unconditionally
         | mutable. Having long-lived mutable references from arbitrary
         | places makes it hard to track object states. It makes easier to
         | end up in unexpected states, and accidentally violate
         | invariants from subroutine code, which means bugs.
         | 
         | Also, class-like functionality conflates data-layout,
         | privacy/encapsulation, API, namespacing and method dispatch
         | into a single thing. Untangling that knot and having control
         | over these features separately might make a better design.
        
           | jtrueb wrote:
           | I agree this is useful. I also think it isn't the end of the
           | world to support some semblance of OOP in Rust.
           | 
           | If it helps you ship the business logic, sometimes it's okay
           | to concede some performance or other cost.
        
             | BrainInAJar wrote:
             | "sometimes" is doing a lot of work there.
             | 
             | Sometimes it is, but also sometimes it isn't, and Rust at
             | least gives you a choice (you can use Arc all over the
             | place, or if performance is critical you can be more
             | careful about specifying lifetimes)
        
               | jtrueb wrote:
               | Arc doesn't give you the choice until this upcasting
               | feature lands in stable.
        
           | mightyham wrote:
           | You make some really good criticisms of OOP language design.
           | I take issue with the part about garbage collecting, as I
           | don't think your points apply well to tracing garbage
           | collectors. In practice, the only way to have "memory leaks"
           | is to keep strong references to objects that aren't being
           | used, which is a form of logic bug that can happen in just
           | about any language. Also good API design can largely
           | alleviate handling of external resources with clear lifetimes
           | (files, db connections, etc), and almost any decent OOP
           | languages will have a concept of finalizers to ensure that
           | the resources aren't leaked.
        
             | tialaramex wrote:
             | Finalizers are crap. Having some sort of do-X-with-resource
             | is much better but now you're back to caring about resource
             | ownership and so it's reasonable to ask what it was that
             | garbage collection bought you.
             | 
             | I agree with your parent that these sort of accidental
             | leaks _are_ more likely though of course not uniquely
             | associated with, having a GC so that you can forget who
             | owns objects.
             | 
             | Suppose we're developing a "store" web site with an OO
             | language - every _Shopper_ has a reference to _Previous
             | Purchases_ which helps you guide them towards products that
             | complement things they bought before or are logical
             | replacements if they 're consumables. Now of course those
             | _Previous Purchases_ should refer into a _Catalog_ of
             | products which were for sale at the time, you may not sell
             | incandescent bulbs today but you did in 2003 when this
             | shopper bought one. And of course the _Catalog_ needs
             | _Photograph_ s of each product as well as other details.
             | 
             | So now - without ever explicitly meaning this to happen -
             | when a customer named Sarah just logged in, that brought
             | 18GB of JPEGs into memory because Sarah bought a USB mouse
             | from the store in spring 2008, and at the time your catalog
             | included 18GB of photographs. No code displays these long
             | forgotten photographs, so they won't actually be kept in
             | cache or anything, but in practical terms you've got a huge
             | leak.
             | 
             | I claim it is easier to make this mistake in a GC language
             | because it's not "in your face" that you're carrying around
             | all these object relationships that you didn't need. In a
             | toy system (e.g. your unit tests) it will work as expected.
        
               | kortilla wrote:
               | Using a website is a strange example because that's not
               | at all how web services are architected. The only pieces
               | that would have images in memory is a file serving layer
               | on the server side and the user's browser.
        
               | sham1 wrote:
               | > Finalizers are crap. Having some sort of do-X-with-
               | resource is much better but now you're back to caring
               | about resource ownership and so it's reasonable to ask
               | what it was that garbage collection bought you.
               | 
               | What garbage collection here brings you, and what it has
               | always brought you here, is to free you from having to
               | think about objects' memory lifetimes. Which are
               | different from other possible resource usages, like if a
               | mutex is locked or whatnot.
               | 
               | In fact, I'd claim that conflating the two as is done for
               | example with C++'s RAII or Rust Drop-trait is extremely
               | crap, since now memory allocations and resource
               | acquisition are explicitly linked, even though they don't
               | need to be. This also explains why, as you say,
               | finalizers are crap.
               | 
               | Things like Python's context managers (and with-blocks),
               | C#'s IDisposable and using-blocks, and Java's
               | AutoCloseables with try-with-resources handle this in a
               | more principled manner.
               | 
               | > I agree with your parent that these sort of accidental
               | leaks _are_ more likely though of course not uniquely
               | associated with, having a GC so that you can forget who
               | owns objects.  > > Suppose we're developing a "store" web
               | site with an OO language - every _Shopper_ has a
               | reference to _Previous Purchases_ which helps you guide
               | them towards products that complement things they bought
               | before or are logical replacements if they 're
               | consumables. Now of course those _Previous Purchases_
               | should refer into a _Catalog_ of products which were for
               | sale at the time, you may not sell incandescent bulbs
               | today but you did in 2003 when this shopper bought one.
               | And of course the _Catalog_ needs _Photograph_ s of each
               | product as well as other details.
               | 
               | Why are things like the catalogues of previous purchases
               | necessary to keep around? And why load them up-front
               | instead of loading them lazily if you actually _do_ need
               | a catalogue of incandescent light bulbs from 2003 for
               | whatever reason?
               | 
               | > So now - without ever explicitly meaning this to happen
               | - when a customer named Sarah just logged in, that
               | brought 18GB of JPEGs into memory because Sarah bought a
               | USB mouse from the store in spring 2008, and at the time
               | your catalog included 18GB of photographs. No code
               | displays these long forgotten photographs, so they won't
               | actually be kept in cache or anything, but in practical
               | terms you've got a huge leak.
               | 
               | I'm sorry, what!? Who is this incompetent engineer you're
               | talking about? First of all, why are we loading the
               | product history of Sarah when we're just showing whatever
               | landing page we show whenever a customer logs in? Why are
               | we loading the whole thing, instead of say, the last
               | month's purchases? Oh, and the elephant in the room:
               | 
               | WHY THE HELL ARE WE LOADING 18 GB OF JPEGS INTO OUR
               | OBJECT GRAPH WHEN SHOWING A GOD-DAMN LOGIN LANDING PAGE!?
               | Instead of, you know, AT MOST JUST LOADING THE LINKS TO
               | THE JPEGS INSTEAD OF THE ACTUAL IMAGE CONTENTS!
               | 
               |  _Nothing_ about this has anything to do with whether a
               | language implementation has GC or not, but whether the
               | hypothetical engineer in question, that wrote this damn
               | thing, knows how to do their job correctly or not.
               | 
               | > I claim it is easier to make this mistake in a GC
               | language because it's not "in your face" that you're
               | carrying around all these object relationships that you
               | didn't need. In a toy system (e.g. your unit tests) it
               | will work as expected.
               | 
               | I don't know, if the production memory profiler started
               | saying that there's occasionally a spike of 18 GB taken
               | up by a bunch of Photograph-objects, that would certainly
               | raise some eyebrows. And especially since in this case
               | they were made by an insane person, that thought that
               | storing the JPEG data in the objects themselves is a sane
               | design.
               | 
               | ---
               | 
               | As you said above, this is very much not unique to
               | language implementations with GC. Similar mistakes can be
               | made for example in Rust. And you may say that no
               | competent Rust developer would do this kind of a mistake.
               | And that's almost certainly true, since the scenario is
               | insane. But looking at the scenario, the person making
               | the shopping site was clearly not competent, because they
               | did, well, that.
        
               | vlovich123 wrote:
               | Why do you treat memory allocations as a special resource
               | that should have different reasoning about lifetime than
               | something like a file resource, a db handle, etc etc?
               | Sure if you don't care about how much memory you're using
               | it solves a small niche of a problem for a lot of
               | overhead (both CPU and memory footprint) but Rust does a
               | really good job of making it easy (and you rarely / never
               | really have to implement Drop).
               | 
               | The context managers and stuff are a crutch and
               | admittance that the tracing GC model is flawed for non
               | memory use cases.
        
               | ablob wrote:
               | Memory is freed as soon as the program closes by the
               | operating system. If you have an open connection to a
               | database they might expect you to talk to them before you
               | close the handle (for example, to differentiate between
               | crash and normal shutdown). Any resource shared between
               | programs might have a protocol that needs to be followed
               | which the operating system might not do for you.
               | 
               | The GC model only cares about memory, so I don't really
               | understand what you mean by "flawed for non memory use
               | cases". It was never designed for anything other than
               | managing memory for you. You wouldn't expect a car to be
               | able to fly, would you?
               | 
               | I personally like the distinction between memory and
               | other resources. If you look hard enough I'm sure you'll
               | find ways to break a model that conflates the two.
               | Similar to this, the "everything is a file" model breaks
               | down for some applications. Sure, a program designed to
               | only work with files might be able to read from a
               | socket/stream, but the behavior and assumptions you can
               | make vary. For example, it is reasonable to assume that a
               | file has a limited size. Imagine you're doing a
               | "read_all" on some video-feed because /dev/camera was the
               | handle you've been given. I'm sure that would blow up the
               | program.
               | 
               | In short, until there is a model that can reasonably
               | explain why memory and other resources can be homogenized
               | into one model without issue, I believe it's best to
               | accept the special treatment.
        
               | Mawr wrote:
               | Because 99.9% of the time the memory allocation I'm doing
               | is unimportant. I just want some memory to somehow be
               | allocated and cleaned up at some later time and it just
               | does not matter how any of that happens.
               | 
               | Reasoning about lifetimes and such would therefore be
               | severe overkill and would occupy my mental faculties for
               | next to zero benefit.
               | 
               | Cleaning up other resources, like file handles, tends to
               | be more important.
        
               | im3w1l wrote:
               | Agree on Finalizers being crap. Do x with resources is
               | shit too though because I don't want indentation hell.
               | Well I guess you could solve it by introducing a do-x-
               | with variant that doesn't open a new block but rather
               | attaches to the surrounding one.
        
           | jghn wrote:
           | > Garbage collection, common in many OOP languages
           | 
           | I would argue this is correlation, not causation. And of the
           | many flaws one can raise with OOP, that GC is pretty low on
           | that list.
        
         | __s wrote:
         | Do you consider Go OOP?
        
         | dullcrisp wrote:
         | I love how OOP is considered a slur on here. It's no wonder
         | Alan Kay doesn't come back.
        
           | Rexxar wrote:
           | Why do you interpret this comment as anti-OOP ?
           | 
           | I see it has criticising rust choice to not do OOP at the
           | beginning to finally do it piece by price and that it would
           | have probably be better for the language to embrace it from
           | start.
        
             | dullcrisp wrote:
             | I don't know how to interpret the comment, but that seems
             | to be how the other responses interpret it.
        
           | bfrog wrote:
           | Inherit is the first step to tell.
        
         | Xeoncross wrote:
         | Are we talking about functional OOP or class-less OOP or
         | JavaScript's prototypal version of inheritance in OOP or Javas
         | functions-are-evil OOP or some other version of OOP?
         | 
         | Object-Oriented Programming is a spectrum with varying degrees
         | of abuse.
        
         | bigstrat2003 wrote:
         | OOP is a good thing, not a bad thing. It enables you to use
         | objects to represent relationships easily in a way that other
         | paradigms don't. I agree that the trendiness of OOP was stupid
         | (it isn't the right approach to every situation and it was
         | never going to solve all our problems), but the way it's trendy
         | to hate on OOP now is equally stupid. It's a good tool that
         | sometimes makes sense and sometimes doesn't, not a savior or a
         | devil.
        
           | vlovich123 wrote:
           | "Inheritance" of interfaces good *. Inheritance of stateful
           | objects bad - composition is much better. The OOP model that
           | Rust supports only supports the good kind of OOP and doesn't
           | support the bad kind.
           | 
           | * technically rust doesn't have interface inheritance but you
           | can treat it that way and it'll mostly look like that in an
           | abstract sense.
        
             | jandrewrogers wrote:
             | I agree that composition is much better than inheritance
             | for most typical code cases. However, there are cases where
             | inheritance is absolutely the correct model and not having
             | inheritance makes for worse code. I may not use inheritance
             | very often (it rarely makes sense in a systems context) but
             | it is nice to have it when it is unambiguously the right
             | tool for the job.
             | 
             | Most code paradigms exist because there is some code
             | context where they are nearly ideal. Someone invented them
             | to handle that case efficiently and elegantly. If you write
             | diverse software long enough you will come across all of
             | those contexts in real code.
             | 
             | I'm the opposite of a model purist. I have a strong
             | preference for a languages that let you efficiently switch
             | models on a very granular basis as may be useful in the
             | moment.
        
             | zozbot234 wrote:
             | > The OOP model that Rust supports only supports the good
             | kind of OOP and doesn't support the bad kind.
             | 
             | Well, almost. You can actually express "the bad kind of
             | OOP" (i.e. implementation inheritance) in Rust quite
             | elegantly via the generic typestate pattern! But that's
             | still good, because it reveals the inherent complexity of
             | your 'inheritance hierarchy' very clearly by modeling every
             | part of it as a separate, possibly generic type. Hence why
             | it's very rarely used in Rust, with interface inheritance
             | being preferred instead. It mostly gets used in cases where
             | the improved static checking made possible by the
             | "typestate" pattern can be helpful, which has little to do
             | with "OOP" design as generally understood.
        
         | echelon wrote:
         | Rust has had significant OOP features since the beginning.
         | Trait methods, dynamic dispatch, inheritance, bounds, etc.
        
         | quotemstr wrote:
         | There's this dynamic in the industry in which a brash young
         | project comes out swinging against some established technique
         | and commits early to its opposite. Then, as the project
         | matures, its authors begin to understand _why_ the old ways
         | were the way they were and slowly walk back their early
         | stridency --- usually without admitting they 're doing it.
         | 
         | Consider Linux. Time was, metaprogramming was BAD, C was all
         | you needed, we didn't need dynamic CPU schedulers, we didn't
         | need multiple LSMs, and we sure as hell didn't need ABI
         | stability. Now we have forms of all of these things (for the
         | last, see CO-RE), because as it turns out, they're actually
         | good.
         | 
         | In Python, well, turns out multiprocessing isn't all you need,
         | and a free-threaded Python has transitioned from impossible and
         | unwanted to exciting and imminent.
         | 
         | In transfer encodings, "front end" people thought that JSON was
         | all you needed. Schemas? Binary encodings? Versioning?
         | References? All for those loser XML weenies. Well, who's the
         | weenie now?
         | 
         | And in Rust? Well, let's see. Turns out monomorphization isn't
         | all you need. Turns out that it is, in fact, occasionally
         | useful to unify an object and its behavior in a runtime-
         | polymorphic way. I expect yeet_expr to go through eventually
         | too. Others are trying to stabilize (i.e. ossify) the
         | notionally unstable ABI, just like they did to poor C++, which
         | is now stuck with runtime pessimization because somebody is too
         | lazy to recompile a binary from 2010.
         | 
         | As surely as water will wet and fire will burn, the gods of
         | Java with stupidity and complexity return.
        
           | Rusky wrote:
           | > And in Rust? Well, let's see. Turns out monomorphization
           | isn't all you need. Turns out that it is, in fact,
           | occasionally useful to unify an object and its behavior in a
           | runtime-polymorphic way. I expect yeet_expr to go through
           | eventually too. Others are trying to stabilize (i.e. ossify)
           | the notionally unstable ABI, just like they did to poor C++,
           | which is now stuck with runtime pessimization because
           | somebody is too lazy to recompile a binary from 2010.
           | 
           | Not to make an argument either way on your general point, but
           | these are really bad examples for Rust if you look at the
           | specifics:
           | 
           | Monomorphization was never the only option. Trait objects and
           | vtables predate the RFC process itself. Early Rust wanted
           | _more_ dynamic dispatch than it eventually wound up with.
           | 
           | The idea of a "throw"-like operator was introduced at the
           | _same time_ as the `?` operator and `try` blocks:
           | https://rust-lang.github.io/rfcs/0243-trait-based-
           | exception-... (Okay, technically `?` was proposed one month
           | previously.)
           | 
           | All the various initiatives related to stable ABI are focused
           | on opt-in mechanisms that work like `#[repr(C)]` and `extern
           | "C"`.
           | 
           | The only way to interpret these as examples of "brash young
           | project walks back its early stridency as it ages" is if you
           | ignore the project's actual reasoning and design choices in
           | favor of the popular lowest-common-denominator Reddit-
           | comment-level understanding of those choices.
        
           | pcwalton wrote:
           | > Turns out monomorphization isn't all you need. Turns out
           | that it is, in fact, occasionally useful to unify an object
           | and its behavior in a runtime-polymorphic way.
           | 
           | This actually gets the history backwards. Ancient Rust tried
           | to do generics in a fully polymorphic way using intensional
           | type analysis, like Swift does. We switched to
           | monomorphization reluctantly because of the code bloat,
           | complexity of implementation, and performance problems with
           | intensional type analysis. "dyn Trait" was always intended to
           | be an alternative that code could opt into for runtime
           | polymorphism.
        
       | mmastrac wrote:
       | The important change here appears to be that the internal
       | representation of a vtable is now guaranteed to have its
       | supertraits laid out in some predictable prefix form to its own
       | methods.
       | 
       | EDIT: or if this is not possible, a pointer to the appropriate
       | vtable is included. I assume this must be for diamond trait
       | inheritance.
        
         | kibwen wrote:
         | _> the internal representation of a vtable is now guaranteed to
         | have its supertraits laid out in some predictable prefix form
         | to its own methods._
         | 
         | Importantly, note that the specifics of the vtable layout are
         | not guaranteed, only the general property that the layout must
         | be amenable to supporting this.
        
           | the_mitsuhiko wrote:
           | And for extra context the RFC lays out the current design and
           | future options: https://github.com/rust-
           | lang/rfcs/blob/master/text/3324-dyn-...
        
         | nialv7 wrote:
         | IIRC vtable happens to already be laid out the required way
         | (barring some corner cases maybe, correct me if I am wrong),
         | this RFC just made that official.
         | 
         | And for diamond patterns vtable entries are duplicated.
        
       | fcantournet wrote:
       | Any are you Debug ? Are you DebugAny ?
        
         | 867-5309 wrote:
         | hit_by() ? struck_by() ?
        
           | Ygg2 wrote:
           | a_smooth_partitional()!
        
       | aerzen wrote:
       | I like rust _because_ Any is inconvenient. It pushes you to
       | implement things with static typing.
        
         | striking wrote:
         | You may want to review the article linked from within the
         | story. The original motivation here is not just to defeat the
         | typechecker.
         | 
         | It begins:
         | 
         | > Let's say you want to have a wrapper around a boxed any that
         | you can debug.
        
       | trashface wrote:
       | This example shows how it works for one trait, Debug, but what if
       | you have a type that (might) implement multiple traits A, B,
       | and/or C? It isn't clear to me if that is possible, unless the
       | type implements all of those traits. What I'd like to do is have
       | some base trait object and query it to see if it supports other
       | interfaces, but also not have to have stub "I don't actually
       | implement this" trait impls for the unsupported ones. A bit like
       | how I might use dynamic_cast in c++.
       | 
       | (I believe I understand that in rust this has not historically
       | been possible because rust doesn't have inheritance, so, there
       | can be only one vtable for a type, rather than an chain like you
       | might have in c++.)
        
         | vlovich123 wrote:
         | I think you've just confused how traits are used. They're more
         | like Java interfaces and there's no inheritance - if you have
         | trait A: B it means whatever type implements a also separately
         | and explicitly has to implement B. Multiple traits would work
         | similarly - either the downcast would work if the type
         | implements a trait or you'd get back a None when you try to
         | downcast.
        
         | mmastrac wrote:
         | You can do something akin to QueryInterface from COM, though
         | it's a bit verbose.
         | 
         | https://play.rust-lang.org/?version=beta&mode=debug&edition=...
         | (ninja edited a few times with some improvements)
         | 
         | With a bit of API massaging, this could be improved quite a bit
         | in terms of ergonomics. The challenge is that traits often
         | require concrete structs if you want them to participate in
         | dynamic typing.
         | 
         | Basically you can now make something like `Arc<dyn
         | QueryInterface>` work, where multiple traits are available
         | through a `query_interface`:                   fn main() {
         | let x = Arc::new(X);             let y = Arc::new(Y);
         | let any = [x as Arc<dyn QueryInterface>, y as _];
         | for any in any {                 if let Some(a) =
         | any.query_interface::<dyn A>() {                     a.a();
         | }                 if let Some(b) = any.query_interface::<dyn
         | B>() {                     b.b();                 }
         | }         }
        
         | dathinab wrote:
         | the article isn't very good for anyone not already familiar
         | with the problem
         | 
         | > What I'd like to do is have some base trait object and query
         | it to see if it supports other interfaces
         | 
         | > rust doesn't have inheritance
         | 
         | rust traits and lifetimes have inheritance, kinda, (through
         | rust types do not)
         | 
         | E.g. `trait A: Debug + Any` means anything implementing A also
         | implements Debug and Any. This is a form of interference, but
         | different to C++ inheritance.
         | 
         | This is why we speaking about upcasts when casting `&dyn A as
         | &dyn Debug` and downcast when trying to turn a `&dyn A` into a
         | specific type.
         | 
         | But because this inheritance only is about traits and the only
         | runtime type identifying information rust has is the
         | `Any::type_id()` the only dynamic cast related querying you can
         | do is downcasting. (Upcasting is static and possible due to
         | changes to the vtable which allow you to turn a `&dyn A` vtable
         | into a `dyn Any`/`dyn Debug` vtable (in the example below).
         | 
         | The part of bout downcast form the article you can ignore, it's
         | some nice convenient thing implicitly enabled by the upcasting
         | feature due to how `downcast_ref` works.
         | 
         | This https://play.rust-
         | lang.org/?version=beta&mode=debug&edition=... might help.
         | 
         | So I think what you want might not be supported. It also is
         | very hard to make it work in rust as you would conceptually
         | need a metadata table for each type with pointer to a `dyn`
         | vtable for each object safe trait the type implements and then
         | link it in every vtable. The issue is concrete rust traits can
         | be unbound e.g. a type `C` might implement `T<bool>` and
         | `T<T<bool>>` and `T<T<T<bool>>>` up to infinity :=) So as long
         | as you don't have very clever pruning optimizations that isn't
         | very practical and in general adds a hidden runtime cost in
         | ways rust doesn't like. Like e.g. if we look at `dyn T` it e.g.
         | also only contains runtime type information for up casting if
         | `T: SomeTrait` and downcasting if `T: Any` and the reason up
         | casting took like 5+ years is because there are many subtle
         | overhead trait offs between various vtable representations for
         | `C: A+B` which might also affect future features etc.
        
         | SkiFire13 wrote:
         | > This example shows how it works for one trait, Debug, but
         | what if you have a type that (might) implement multiple traits
         | A, B, and/or C? It isn't clear to me if that is possible,
         | unless the type implements all of those traits.
         | 
         | Yeah, you can do the same trick if you care about types that
         | implement all of A, B and C.
         | 
         | > What I'd like to do is have some base trait object and query
         | it to see if it supports other interfaces, but also not have to
         | have stub "I don't actually implement this" trait impls for the
         | unsupported ones.
         | 
         | Currently this is not possible, though it might be possible in
         | the future once Rust gets specialization. With that you would
         | basically be able to write the "I don't actually implement
         | this" stub automatically. The catch however would be that this
         | can only work for `'static` types, since such specialization
         | for non-`'static` types is unsound.
         | 
         | > I believe I understand that in rust this has not historically
         | been possible because rust doesn't have inheritance, so, there
         | can be only one vtable for a type, rather than an chain like
         | you might have in c++.
         | 
         | The issue is kinda the opposite. In Rust types can implement
         | any number of traits (even infinite!), so they would require an
         | potentially an infinite amount of vtables if implemented like
         | in C++, which is just not possible. So instead this is splitted
         | from types and moved to trait objects, which allow to carry a
         | vtable for one specific trait.
        
       | kibwen wrote:
       | _> Even though DebugAny inherits from Any_
       | 
       | I'm going to push back against this terminology for the sake of
       | people who don't know Rust and are coming from traditional OO
       | languages.
       | 
       | Rust traits don't "inherit" from other traits, they "require"
       | other traits.
       | 
       | So if I have two traits, where one requires the other:
       | trait Foo {}              trait Bar: Foo {}
       | 
       | That doesn't add Foo's methods to Bar's list of methods, like you
       | might expect from the term "inheritance".
       | 
       | Instead, it just means that it's not possible to implement Bar
       | for a type unless Foo is also separately implemented for that
       | type.
       | 
       | Despite this it's still accurate to say that this enables a
       | supertype/subtype relationship in certain contexts, but be
       | careful because Rust isn't a traditionally OO language, so that
       | may not always be the most useful framing.
        
         | d4mi3n wrote:
         | What's the practical distinction here? I agree with you that
         | rust isn't OOP, but for the sake of communication and
         | understanding--what's the practical difference between
         | requiring a trait and inheriting from it?
        
           | Rusky wrote:
           | It means you can't just write `impl Bar for MyType` and get
           | Foo pulled in automatically. You have to write both `impl`s
           | yourself.
           | 
           | The inheritance-like syntax is shorthand for `trait Bar where
           | Self: Foo`, and bounds like this can show up in lots of other
           | places, where they follow the same rules: `MyType` has to
           | impl all of them, and the traits and bounds can have fairly
           | arbitrary (or at least un-tree-like) relationships.
           | 
           | The upcasting thing is a rare exception to the idea that this
           | `Self: Foo` bound is just like any other bound.
        
           | PeterWhittaker wrote:
           | If Bar inherited from Foo, Bar would have Foo's methods. So
           | if you implemented Bar for Gum, Gum would get Bar and Foo.
           | 
           | But Bar requiring Foo means that if you want to use Gum in a
           | place that expects Bar, Gum must have both Bar's methods and
           | Foo's methods.
           | 
           | In some cases, you might be able to derive some of those.
        
           | pwdisswordfishz wrote:
           | For one, each trait has a separate namespace for its items,
           | so in particular Foo::quux and Bar::quux are distinct even
           | when one trait "inherits" from the other.
        
           | pjmlp wrote:
           | Rust isn't classical Java OOP, that many keep conflating with
           | OOP, there are many ways of OOP type systems in computer
           | science.
           | 
           | I have easily translanted the raytracing in one weekend from
           | its OOP design in C++, into a similar OOP design in Rust.
        
         | layer8 wrote:
         | It's not clear from that description how it differs from
         | interface inheritance in OOP.
         | 
         | In an interface/class hierarchy _A_ < _B_ < _C_ , _C_ can be an
         | abstract class that only implements _B_ 's methods and not _A_
         | 's.
        
           | kibwen wrote:
           | In Rust, an implementation of a trait never implements
           | methods that weren't defined on that trait explicitly. In
           | your example, an implementation of C inherits from B and A,
           | but only implements B's methods. In Rust, an implementation
           | of C _cannot_ itself implement B 's or A's methods; it is
           | merely allowed to assume that they have been implemented
           | elsewhere. This also means that subtraits cannot override the
           | behavior of methods defined on supertraits.
        
             | layer8 wrote:
             | Okay, but doesn't Bar nevertheless inherit Foo's methods in
             | the sense of interface inheritance? Or does code that gets
             | passed a Bar not get access to Foo's methods?
        
               | the_mitsuhiko wrote:
               | > does code that gets passed a Bar not get access to
               | Foo's methods
               | 
               | Prior to the current version of Rust it was impossible to
               | access methods on `Foo` when a `&dyn Bar` was passed.
               | With the current beta version you can upcast.
        
               | ogoffart wrote:
               | > Prior to the current version of Rust it was impossible
               | to access methods on `Foo` when a `&dyn Bar` was passed.
               | 
               | This has been working since Rust 1.0, I think:
               | trait Foo {             fn foo(&self);         }
               | trait Bar : Foo {}              impl Foo for () {
               | fn foo(&self) { println!("Hello") }         }
               | impl Bar for () {}              fn xxx(x: &dyn Bar) {
               | x.foo();         }              pub fn main() {
               | xxx(&());         }
               | 
               | > With the current beta version you can upcast
               | 
               | Right. Now you can convert from `&dyn Bar` to `&dyn Foo`
               | which wasn't possible before.
        
               | the_mitsuhiko wrote:
               | Inherent method implementations were unavailable. (Eg:
               | impl dyn Foo) and then call this via Bar.
        
           | zamalek wrote:
           | It might be better to think of it this way: types in Rust do
           | not implement traits, traits are implemented for types. It
           | might seem subtle, but its not: a trait can be implemented
           | for a type without that type's knowledge (obviously taking
           | care for the orphan rule). Traits are also implemented via
           | pattern matching (Into being implemented for all Froms is the
           | most trivial example). Go's interfaces come closer to Rust
           | traits than those from OOP languages.
           | 
           | I've experienced a lot of fear in other people, especially
           | when interviewing, about Rust not being OOP. Very smart
           | people seem to be coming up with carve-outs to keep that
           | religion alive. Try letting go of OOP for a month or two, and
           | you'll find that you're better off letting OOP be someone
           | else's problem.
        
             | layer8 wrote:
             | I think we have different notions of OOP. OOP without
             | implementation inheritance and with extension
             | interfaces/methods is still very much OOP to me.
        
           | Starlevel004 wrote:
           | > It's not clear from that description how it differs from
           | interface inheritance in OOP.
           | 
           | Remember, when people talk about "OOP" what they're
           | _actually_ talking about is Java EE 6.
        
         | the_mitsuhiko wrote:
         | The terminology that Rust uses is that "Foo" is a supertrait of
         | "Bar". I understand that the docs so not call it inheritance,
         | but from my experience at least people call this "trait
         | inheritance" quite commonly.
        
         | SkiFire13 wrote:
         | > That doesn't add Foo's methods to Bar's list of methods, like
         | you might expect from the term "inheritance".
         | 
         | This is not completly correct. It's true you won't e.g. be able
         | to implement `Foo` and specify `Bar`'s method in it, however it
         | does mean that e.g. `dyn Foo` will support `Bar`'s methods.
         | 
         | > Despite this it's still accurate to say that this enables a
         | supertype/subtype relationship in certain contexts
         | 
         | It does enable something that looks like subtyping because
         | you're allowed to use something that implements `Foo` when
         | something that implements `Bar` is expected. However this is
         | not called subtyping in Rust terms; subtyping in Rust exists
         | but is unrelated to traits (it's only affected by lifetimes)
        
       | the__alchemist wrote:
       | Rust continues to blow my mind. It's been my most-used language
       | for ~5 years, and I have no idea what this article is describing!
        
       ___________________________________________________________________
       (page generated 2025-03-30 23:00 UTC)