[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)