[HN Gopher] in Rust, methods should be object safe
       ___________________________________________________________________
        
       in Rust, methods should be object safe
        
       Author : fanf2
       Score  : 54 points
       Date   : 2024-05-30 10:42 UTC (2 days ago)
        
 (HTM) web link (nora.codes)
 (TXT) w3m dump (nora.codes)
        
       | palata wrote:
       | It is a nice read!
       | 
       | I am not super familiar with the definition of "object-safe" (I
       | read the docs but the definition is not completely trivial for
       | me).
       | 
       | Does it work to say that _an "object-safe" method mutates the
       | object_? That's what happens when you take `&mut self`, right?
       | 
       | But if I understood this correctly, I guess it means that a
       | function without side-effect in Java should not really count as a
       | method? Because in Java I call it a method whether it has a side-
       | effect or not. Which corresponds exactly to the definition of
       | `method` in the Rust docs, I think [1]. In C++, I would think
       | that this is the difference between a const (no side-effect) and
       | a non-const (side-effect) method.
       | 
       | [1]: https://doc.rust-lang.org/reference/items/associated-
       | items.h...
        
         | steveklabnik wrote:
         | Not every trait can be made into a trait object. Traits that
         | are "object safe" can be made into a trait object. Nothing to
         | do with mutation, or side effects.
        
           | palata wrote:
           | hmm... so there are object-safe methods and non-object-safe
           | methods, and you can make a `Vec<YourType>` as long as
           | `YourType` does not implement any non-object-safe methods?
        
             | steveklabnik wrote:
             | Yes, except that you need a layer of indirection to make a
             | trait object, so like Vec<Box<YourTrait>> or similar.
        
             | xvedejas wrote:
             | You might be confusing types and traits (typeclasses) here;
             | it's not about `YourType` but rather when you want to make
             | a Vec of heterogenous types that share a trait. That's when
             | the question of object safety (and needing a vtable)
             | arises.
        
             | sshine wrote:
             | > _there are object-safe methods and non-object-safe
             | methods_
             | 
             | Yes. In the reference, below the definition, there is a
             | list of examples of:                 - Traits that are
             | object-safe with object-safe methods       - Traits that
             | are object-safe with non-dispatchable methods       -
             | Traits that are not object-safe
             | 
             | https://doc.rust-
             | lang.org/reference/items/traits.html#object...
             | 
             | > _you can make a `Vec <YourType>` as long as `YourType`
             | does not implement any non-object-safe methods?_
             | 
             | It is traits that are object safe, not concrete types.
             | 
             | You cannot make a `Vec<YourTrait>`, you have to make a
             | `Vec<T> where T: YourTrait` or a `Vec<Box<dyn YourTrait>>`
             | or similar.
             | 
             | You only need object safety if you're passing around values
             | dynamically cast to a trait. That's not a required
             | programming pattern.
        
               | josephg wrote:
               | > You only need object safety if you're passing around
               | values dynamically cast to a trait. That's not a required
               | programming pattern.
               | 
               | Yep. In 3+ years of writing rust full time, I've still
               | never used `dyn Trait` in any of its forms. A vec where
               | every item is independently heap allocated? Why? And
               | yikes! That would perform terribly.
        
               | guipsp wrote:
               | You have likely used dyn result, possibly without
               | realising it, if you have used anyhow
        
               | filleduchaos wrote:
               | > A vec where every item is independently heap allocated?
               | 
               | Just because that's the example given (by someone who's
               | trying to understand the concept, no less) doesn't mean
               | that that's the only thing that the language feature is
               | usable for.
               | 
               | Trait objects (read: runtime polymorphism) have their
               | pros and cons just as generics (read: parametric
               | polymorphism) have theirs. Not everything can be known at
               | compile time, and that's perfectly okay.
        
               | charrondev wrote:
               | I mean if you know all the concrete types ahead of time
               | you can put them in an enum.
               | 
               | It kind of makes me we wish there was a keyword that
               | would grab all implementations of that trait being
               | compiled and do the same thing though. At least for a
               | statically linked codebase it could figure out all the
               | sizes of the implementations
        
         | throwup238 wrote:
         | It has to do with type erasure and knowing how big a type is in
         | memory, not mutation. With traits, you can erase the
         | implementing type and get back a vtable of a trait's method -
         | called a trait object.
         | 
         | A trait is object-safe as long as no trait method returns the
         | implementing type (like T::clone() -> T). Once it's a trait
         | object, T is no longer known and the compiler doesn't know how
         | much memory to allocate on the stack or heap, which makes it
         | impossible to use. That's why trait objects have to be boxed or
         | behind references - all pointers have a known size.
        
           | diarrhea wrote:
           | > A trait is object-safe as long as no trait method returns
           | the implementing type (like T::clone() -> T)
           | 
           | That, and much more: https://doc.rust-
           | lang.org/reference/items/traits.html#object... .
        
       | sshine wrote:
       | I'll start off by disagreeing with the first sentence:
       | 
       | > _I think we should use "method", in Rust_
       | 
       | Just don't. Object-oriented programming is a 1980s buzzword.
       | Let's help industry catch up.
       | 
       | I don't care if people call them functions or methods in daily
       | speech. I even do that sometimes, depending on whom I'm talking
       | to.
       | 
       | But let's not make up a highly technical definition of what a
       | Rust method is.
       | 
       | Methods are loosely the same as functions that take a '&self',
       | and only because your brain had that word for it, not because
       | it's a good word.
        
         | alex_lav wrote:
         | > Methods are loosely the same as functions that take a
         | '&self', and only because your brain had that word for it, not
         | because it's a good word.
         | 
         | Isn't this just true of any word with increased specificity?
         | Why call it a square when we already have rectangle?
         | 
         | I'm not really seeing how understanding method vs. function is
         | "highly technical". At least, not more technical than, I don't
         | know, writing Rust code in the first place?
        
           | stouset wrote:
           | It's just not a particularly useful distinction IMO. I have
           | worked in C++, C#, Ruby, and Rust over the past 25 years of
           | programming and outside of academic contexts I have _never_
           | had a discussion with coworkers that hinged upon whether or
           | not a particular function is, was, or ought to be a method.
           | 
           | In academic circles, there's some meaningful distinctions to
           | be made. In software engineering I've yet to find a place
           | where having a very specific name mattered or reduced
           | communication burden.
        
             | Marzepansion wrote:
             | This might be your experiences, but I've never worked with
             | people who called member functions methods in C++. I've
             | seen C# programmers who had never worked with C++ call them
             | that, but in the C++ teams I can't recall an instance of
             | that, even for multi lingual engineers
             | 
             | Though I do tend to work with teams where the average age
             | is higher than the industry average, that might be warping
             | my anecdotal experiences
             | 
             | I have had situations where the distinction matters, and
             | they aren't academic either, but in API design, especially
             | for libraries
        
               | stouset wrote:
               | I've had a similar experience, but if someone called a
               | method a function or vice versa, it's never been unclear
               | what they meant. And it's never been something that
               | people would have felt the need to correct someone on.
               | 
               | I am aware of the distinction and I (probably?)
               | instinctively use the two terms "correctly" the majority
               | of the time. But if I just picked one or the other and
               | used that word 100% of the time I would wager there would
               | be zero confusion, except amongst pedants.
               | 
               | For what it's worth, I say this as an avowed pedant.
        
             | alex_lav wrote:
             | > I have never had a discussion with coworkers that hinged
             | upon whether or not a particular function is, was, or ought
             | to be a method
             | 
             | Because _you_ haven't means the entire industry is wrong?
        
               | stouset wrote:
               | I'm not saying the industry is wrong. The two words have
               | clear and distinct meaning.
               | 
               | My point is only that fretting over the distinction
               | between the words serves nearly zero utility in day to
               | day software engineering. It's like "less than" vs.
               | "fewer". Each has a correct use, but nobody is confused
               | when you use the wrong one. Many confuse the two, to the
               | point that it's becoming less and less wrong to use one
               | when the other should be used. "Method" and "function"
               | are similar.
               | 
               | In twenty years, nobody is going to care that they once
               | had a distinctive purpose because that distinction serves
               | little utility.
        
               | alex_lav wrote:
               | "People will know what I mean, so why bother being
               | correct or specific" is a flimsy argument, to me, as to
               | why a person shouldn't care about using correct and
               | precise language. Nor does it really challenge the need
               | for correct and precise language in the first place.
               | 
               | Small children say things incorrectly and we still
               | understand them. Should we all talk like that?
        
         | verandaguy wrote:
         | Rust-specific nomenclature aside, I think it's useful in
         | technical discussions to distinguish between (object) methods,
         | class methods, and functions if your working language supports
         | any combination of the three.
         | 
         | The word "method" is baked into the official Rust docs[0], and
         | while I personally find the definition in the docs to be
         | satisfying, I don't think the author's argument for narrowing
         | that definition down is unreasonable or (more importantly)
         | unworthy of discussion.                   [0] https://doc.rust-
         | lang.org/rust-by-example/fn/methods.html
        
       | zamalek wrote:
       | Author is correct that Rust is not OOP. It simply has optional
       | dispatch (which does not an OOP language make). I'm sure many of
       | my fellow rustaceans will agree that the lack of OOP is one of
       | the most competitive features of Rust.
        
         | pjmlp wrote:
         | Only those that never picked a Computer Science book on OOP
         | type systems.
        
           | zamalek wrote:
           | Adhod. I have 18 years of professional experience with OOP,
           | all of it a bloody waste of time. Exhibit:
           | https://youtu.be/ns7jP1-SRvw?si=4NyOYebqtobIkFfC
        
             | pjmlp wrote:
             | Apparently not enough to actually understand OOP type
             | theory.
        
         | echelon wrote:
         | Rust _is_ OOP. You can pass  &self, self, etc. You can dynamic
         | dispatch. It has a lot of great OOP features.
         | 
         | What Rust is not: class-based or inheritance-based (discounting
         | default trait impls).
         | 
         | And this is a nice, modern form of OOP that discards orthogonal
         | and useless design baggage.
        
           | zamalek wrote:
           | By that definition nearly every language is OOP, which makes
           | the term useless.
        
             | k__ wrote:
             | Just because all languages adopted OOP features.
        
             | deepsun wrote:
             | Yep. All modern general-purpose languages allow to write in
             | object-oriented style.
        
             | ab5tract wrote:
             | No, it makes the paradigm ubiquitous.
        
         | lowbloodsugar wrote:
         | I always say it differently: OOP is a paradigm. I've done
         | "object oriented programming" in C and in assembly. Might be
         | more correct to say I've done things like polymorphism in C and
         | assembly. By _my_ definition, Rust can do OOP, even without
         | dyn. I tend to take a very low-level view of things, since I
         | was an assembly programmer before I was a C programmer. I think
         | as Rust broadens its footprint, people are going to have to
         | start using dyn more, and as they do, there 'll be new patterns
         | becoming idiomatic. The polymorphism-using-enums pattern is
         | reasonable for tight code like embedded systems, but painful
         | for applications generally.
        
           | zamalek wrote:
           | > The polymorphism-using-enums pattern is reasonable for
           | tight code like embedded systems, but painful for
           | applications generally.
           | 
           | Rust rapidly becomes painful if you try to OOP in it. You
           | would be better off using an OOP language for OOP. It wasn't
           | voted the most loved language because people enjoyed learning
           | lifetimes alongside all the accidental complexity OOP
           | creates.
           | 
           | Dynamic dispatch is mostly seen in two circumstances: the
           | type can't be known at compile time, and reducing code bloat
           | as an optimization step.
        
           | sunshowers wrote:
           | FYI: https://docs.rs/enum_dispatch
           | 
           | This doesn't work for every case of dynamic dispatch (it only
           | works for what I call closed and semi-open universes of
           | choices [1]), but it works for many of them. And there can
           | always be a catch-all variant that uses a trait object.
           | 
           | [1] https://sunshowers.io/posts/open-closed-universes/
        
         | kibwen wrote:
         | Let's be precise about what definition of OOP we're each using.
         | 
         | In college, I (and I'm sure many others here) learned that OOP
         | was about "abstraction, encapsulation, inheritance, and
         | polymorphism".
         | 
         | But in practice, looking at the history of programming
         | paradigms in industry, it seems like the real success of OOP
         | was in dragging us _away from_ "it's fine for mutable state to
         | be global and/or scattered willy-nilly throughout your code"
         | and _towards_ "mutable state should be localized, tightly
         | scoped, and/or coupled to the code that will be mutating it".
         | Being generous to the academic definition, we could say that
         | the only pillar of OOP that really mattered was encapsulation.
         | 
         | And by that definition, since Rust abhors global mutable state
         | (but _not_ mutation in general, as a functional language might
         | do), it 's not unreasonable to say that Rust, even if it
         | doesn't have "objects" as Java or Smalltalk define them, still
         | hews to the most useful parts of OOP.
        
           | logicchains wrote:
           | > "mutable state should be localized, tightly scoped, and/or
           | coupled to the code that will be mutating it
           | 
           | Typical enterprise OOP did the opposite of this, coupling
           | state to unrelated code via inheritance. To quote the creator
           | of Erlang, when you want the banana it gives you the whole
           | forest.
        
           | mrkeen wrote:
           | > towards "mutable state should be localized, tightly scoped,
           | 
           | Ironically, the way to do _actual encapsulation / localized-
           | mutable-state_ is to just use stack variables - the C way.
           | 
           | Fields ( _what OOP added_ ) are leakier and less localized
           | than stack variables.
        
             | kibwen wrote:
             | _> just use stack variables - the C way_
             | 
             | The problem is that C doesn't offer any resistance to using
             | globals rather than stack variables, which led to much of
             | the mess that the OOP revolution of the 80s was supposed to
             | address. For every C program that eschews globals, you have
             | a Toyota Camry, whose control systems famously contained
             | "10,000 global variables"
             | https://news.ycombinator.com/item?id=9643204
        
             | trealira wrote:
             | C still has fields in structures; you just have to pass a
             | structure as the argument to a function. As long as you're
             | okay with syntax like "self->field" or "self.field" (given
             | a parameter called "self"), you can still kind of do OOP,
             | just that no fields are private.
        
           | zamalek wrote:
           | > In college, I (and I'm sure many others here) learned that
           | OOP was about "abstraction, encapsulation, inheritance, and
           | polymorphism".
           | 
           | This is what I was referring to. A problem that follows is
           | that many of the Go4 patterns solve problems that OOP
           | _causes_ in the first place (obviously many others are
           | universal).
        
       | Karellen wrote:
       | // counter_associated.rs         [...]                 let c =
       | Counter::increment(f);
       | 
       | What is `f` here?
        
       | karma_pharmer wrote:
       | "Object-safe" really ought to be called "dyn-safe".
        
       ___________________________________________________________________
       (page generated 2024-06-01 23:02 UTC)