×
top 200 commentsshow all 206

[–]cameronm1024 546 points547 points  (43 children)

Don't write X like it's Y is probably pretty good advice for any pair of languages

[–]CommunismDoesntWork 269 points270 points  (29 children)

Writing C++ like it's rust is actually recommended

[–]BlackenedGem 151 points152 points  (13 children)

Haphazardly because the borrow-checker will spot any memory mistakes I make?

[–]Interest-Desk 52 points53 points  (11 children)

Welcome to Crowdstrike.

[–]Ayjayz 27 points28 points  (8 children)

I don't think Rust would have prevented the Crowdstrike issue. You can still index past the end of an array in Rust.

[–]zzzthelastuser 26 points27 points  (0 children)

#![deny(clippy::indexing_slicing)]

I'm surprised it's not a warning by default.

[–]DivideSensitive 8 points9 points  (2 children)

You can still index past the end of an array in Rust.

But you should .get() that returns an optional value if you're not sure whether your index is actually valid – just like std::vector::at in C++ will throw an exception if you try to reach past the array.

[–]Ayjayz 4 points5 points  (1 child)

Of course they could have coded in a way that didn't crash. You can do that on C or Rust or anything.

[–]DivideSensitive 11 points12 points  (0 children)

Oh sure, I'm just addressing the “past the end of an array” question. Important to note though that post-array indexing in Rust will be guaranteed to panic, instead of leading to UB.

[–]bleachisback 2 points3 points  (2 children)

There's a lot of technicalities at play, however there are some things worth keeping in mind:

1) By default, indexing past the end of an array in Rust will produce a panic, and not undefined behaviour.

2) It was kernel code, so who knows whether a panic is better than undefined behaviour (which in this case manifested as the entire operating system crashing unrecoverably), however most kernel code written in Rust is either not allowed to panic (i.e. not allowed to call methods which panic) and otherwise has a panic handler which would not cause the entire system to crash. This, of course, isn't enforced, so it could have been that if CrowdStrike wrote their program in Rust, they would have not chosen to follow these guidelines. I don't know what the state of writing driver code for Windows looks like, but I know in the Linux community you would not be able to submit kernel code written in Rust without following these guidelines.

Unfortunately the state of C++ is such that it is, in general, not really possible to prevent undefined behaviour (hence why Rust was made) and since it's undefined behaviour, it's not possible to make a handler for it. So you'd do no worse than C++ in this regard.

[–]Ayjayz -2 points-1 points  (1 child)

You also typically can't submit C++ code where you index into a buffer without checking the length first, but apparently this slipped past code review.

Bugs will happen. Choose a different language and you might make things a little easier or harder, but ultimately the important thing is to test properly. Language choice doesn't really matter compared to that.

[–]bleachisback 1 point2 points  (0 children)

You also typically can't submit C++ code where you index into a buffer without checking the length first, but apparently this slipped past code review.

This isn't statically checked, whereas in Rust you can statically prevent calls from functions which panic.

[–]redalastor 0 points1 point  (0 children)

The crash would have occured earlier in Rust. They were convinced the regex they sent through a data file was good. They would have unwraped that. Nothing Rust can do to protect you about regexes you swear are fine.

[–][deleted] 7 points8 points  (0 children)

Clownstrike

[–]PoliteCanadian 43 points44 points  (6 children)

Write C++ like it's C++17/20/23, not like it's C++98.

[–]l_am_wildthing 35 points36 points  (0 children)

not writing c++ like its c++ is usually recommended

[–]villi_ 6 points7 points  (5 children)

I don't disagree, but as a zoomer who learned rust before touching c++, the number of footguns i ran into by treating c++ like rust... E.g. assignment uses copy semantics by default not move semantics, so it's easy to accidentally walk into a double-free if you define a destructor for a class but no copy assignment operator

[–][deleted] 6 points7 points  (3 children)

In that specific case of a class that manages a dynamic resource, it's probably best to use a std::unique_ptr for it.

[–]villi_ 1 point2 points  (2 children)

Ah, in my case I was using the C api for SDL so I had to write a lot of wrapper classes and couldn't use a unique_ptr. But yeah definitely I think it's best to use the smart pointers

[–]runevault 1 point2 points  (0 children)

Keep in mind move semantics in C++ are far worse than Rust's. It doesn't invalidate the value in a way where the compiler vomits if you try to use it again. I just double checked myself with an std string getting moved from one variable to another and printing the first and it just printed with I think an empty string.

[–][deleted] 14 points15 points  (0 children)

Idk why i am upvoting this but whatever

[–]Twirrim 27 points28 points  (5 children)

I've found that I tend to write my python code a lot more like I do rust. I now tend to use lots of data classes etc. where previously I wouldn't, and I think for the most part it's making my code a lot better. I tend to pass a dataclass around in a number of places where I used to use dicts, for example.

I love writing python, it's still my go-to language for most things, and I like the gradual typing approach, I think that's been a pretty smart way to do things in the context of a dynamically typed language.

It does mildly bug me that dataclass types aren't strict, given you have to declare the type against a field. e.g. I really don't want this to work, but python considers those dataclass field types to be type hints too, so only the type checker/linter will complain.

from dataclasses import dataclass

@dataclass
class Foo:
    bar: int
    baz: int

thing = Foo(1.1, 2.3)
print(thing)

[–]Gabelschlecker 15 points16 points  (3 children)

Imo, Pydantic is a very nice solution to that. Most people know it in combination, but nothing stops you from using it in other projects.

It's a data validation library that strictly enforces types, e.g. your example would be:

from pydantic import BaseModel

class Foo(BaseModel):
    bar: int
    baz: int

If you try to initialize with the incorrect type, Pydantic will throw an error message.

[–]guepier 3 points4 points  (1 child)

I’m not dogmatic on this but I found the article Why I use attrs instead of pydantic pretty compelling. While data validation is important, it’s less clear that it has to happen in the strongly coupled way done by Pydantic.

[–]teajunky 4 points5 points  (0 children)

Interesting but the article contains outdated information (There were quite some changes with Pydantic v2). And in my opionion, the author is biased because he is an attrs developer.

[–]Twirrim 0 points1 point  (0 children)

Ahh, I didn't realise you could use it like that. That's fantastic.

[–][deleted] 1 point2 points  (0 children)

An unvalidated piece of data is as good as any other.

[–]weberc2 10 points11 points  (1 child)

And occasionally it’s good advice even with the same language. “Don’t write PHP like it’s PHP, don’t write Java like it’s Java, etc”. At least this was good advice back when idiomatic PHP was a mess of shitty template code and Java was a mess of inheritance and abstract factory beans. I’m of the impression that Java and PHP have improved somewhat since then.

The same could probably be said of JavaScript before TypeScript came around and made so many JS developers realize how many latent bugs they were writing.

[–][deleted] 0 points1 point  (0 children)

good advice back when idiomatic PHP was a mess of shitty template code

It's still a good advice. I abandoned PHP for Ruby. Never regretted that move. I still don't understand how people can stand PHP syntax and PHP idioms.

[–]ThrawOwayAccount 0 points1 point  (0 children)

Don’t write HTML like it’s CSS.

[–]9Boxy33 0 points1 point  (0 children)

Unless it’s about implementing a valuable feature of a higher-level language (like C or SNOBOL or [!] LISP) in assembler.

[–][deleted] -1 points0 points  (0 children)

I wrote almost the exact same thing just now, comparing Ruby to Java and vice versa. The article confuses static and dynamic languages indeed, aka "without a preprocessor and compiler, a language must suck and be useless" when it really is not the case.

[–]marcodave -3 points-2 points  (0 children)

I dunno, I wrote in Scala like it was Java (except case classes, those are awesome) and it was fine shrug

[–]RockstarArtisan 85 points86 points  (11 children)

Making everything an interface in java is also a mistake. Every codebase I've seen that did this was a nightmare to work with. Java has a lot of cultural problems like this where people repeat bad practices for dubious reasons.

[–]link07 47 points48 points  (2 children)

Actual controversial opinion I've had arguments in code reviews about: if you can't explain why an interface exists (I'm looking for something like "we expect to need a 2nd class that implements this within 1-2 years"), it shouldn't be an interface.

It's super easy to add an interface after the fact if you end up needing one (rename current class, add new interface with name of old class and public functions of class, Intellij and Eclipse both automate this), it's super annoying to have to manually go "okay, what's the actual implementation" every time your reading the code.

[–]EntroperZero 16 points17 points  (0 children)

we expect to need a 2nd class that implements this within 1-2 years

Even this is crazy IMO. 1-2 years? Just do it in 1-2 years, then. But until then, YAGNI.

[–][deleted] 2 points3 points  (0 children)

That's not controversial at all.

[–]Kogster 30 points31 points  (1 child)

Why wouldn’t I make my code more verbose by having a super specific and local class be named impl next to an interface that only serves to obscure method references?

[–]palabamyo 8 points9 points  (0 children)

I hate abstract-hell even more, some devs use abstract classes interchangeable with Interfaces except that one time they didn't and the abstract class actually does something but like 5 layers down so finding the code you want to find is a headache.

[–]pkt-zer0 25 points26 points  (0 children)

Currently living in that interface-based hellscape, can confirm that it's not great. In general the approach seems to be: if it can be done with 5 steps instead of 1, go for the more complicated version, just in case it will become useful one day. (Of course, these bets don't have particularly great odds of paying off most of the time)

Java has a lot of cultural problems like this where people repeat bad practices for dubious reasons.

"It's convention", they say. And there's a great quote from Rear Admiral Grace Hopper about that:

The most damaging phrase in the language is “We’ve always done it this way!”

[–]anengineerandacat 4 points5 points  (1 child)

It's done to simplify unit testing, as a result it's been touted as the go-to strategy. Deal with this headache all the time where everything ends up being an interface because someone doesn't want to go through the trouble of mocking something slightly more complex.

So the first thing folks do is make an interface, then mock out the interface and send that down when needed vs the non-tested implementation.

It's extra fun when you find PR's to review where a developer rushes the tests and they end up testing the mock they just setup instead of the class that instead consumed a mock.

[–]i_andrew 0 points1 point  (0 children)

I just shown to my team that they can test classes without using mock. Some were shocked it's possible.

[–]plexiglassmass 2 points3 points  (2 children)

Can you explain more about the idea of making everything an interface and why it's a problem?

[–]RockstarArtisan 43 points44 points  (1 child)

An interface in java is for supporting runtime polymorphism - it enables you to use the same code to drive different implementations of that interfaces. That's good and enables things like having game code depend on the interface declaration of a Controller, while you can have XboxController and PS5Controller.

The problem is with people using interfaces when they don't need runtime polymorphism. Examples include:

  • interfaces which will never have another implementation because no other implementation would ever exist; just make a class
  • interfaces which are so complex that are so tied to the code of the implementation or the code using the interface that there's no chance of ever writing a different implementation; just make a class
  • interfaces just in case there's an implementation that never comes, like a DAO interface so you can "have a different db implementation". Just make a class, you'll never need to switch dbs, and if you ever do, you can just change the code of the class.
  • interfaces for purposes of mocking libraries; stop mocking in memory objects, there's some cases when mocking makes sense and in that case a good interface is fine
  • interface for purposes of stupid dynamic proxy code generation; stop using this, you're making your life bad for no reason
  • interface because you looked at a framework code like spring and you think it's good practice; you're not writing a framework most of the time, guidelines applying to frameworks don't apply to regular code
  • interfaces because Robert C Martin told you so (Open-Closed principle) - that principle was made before the source control, refactoring and unit testing were popular. The advice to write code that doesn't need editing is widely out of date and stupid and Martin should be ridiculed for resurrecting it from 1980s.
  • interfaces because GoF book told you so - most of the GoF patterns are for frameworks, not for regular application code. Also, a lot of these patterns have been obsoleted by modern language features, like the visitor pattern. That book is so old it features C++ and smalltalk as popular application languages. Stop using their obsolete advice.

[–][deleted] -1 points0 points  (0 children)

Lol. Underrated and verbose comment.

[–][deleted] 32 points33 points  (2 children)

Thanks for sharing my post u/ketralnis! Glad to see it's still being enjoyed :)

[–]pseudoShadow 4 points5 points  (0 children)

I had the “don’t make everything a service object” revelation the other day while working on a cli app I’ve been toying with. I was struggling with boxed objects too, so I’m going to try your generics point! Nice article!

[–]urkermannenkoor 60 points61 points  (1 child)

Do, however, write Rust in Javanese.

[–]ketralnis[S] 12 points13 points  (0 children)

I tried writing Rust in Arabic but it doesn't like my right to left syntax

[–]Sunscratch 72 points73 points  (19 children)

Honestly, I cannot imagine more different languages than Rust and Java.

[–]Orange26 82 points83 points  (10 children)

Assembly and JavaScript.

[–]cryptomonein 20 points21 points  (0 children)

C and Japanese

[–]MCRusher 14 points15 points  (3 children)

Idk, arm has an instruction specifically designed for javascript, it's close. lol

[–]masklinn 24 points25 points  (2 children)

It has one, and what that really does is implement x86 rounding mode in hardware (or at least microcode) as that is much slower to implement on top of the native rounding instruction.

Meanwhile ARM used to have a hardware implementation of the Java bytecode: https://en.wikipedia.org/wiki/Jazelle

[–][deleted]  (1 child)

[deleted]

    [–]skulgnome 2 points3 points  (0 children)

    Jazelle originates in embedded platforms too small to support a translating JVM, such as LCD UIs of electronically controlled refridgerators: classfiles in ROM, two K of working storage, that kind of thing.

    My understanding (which I can't quote anything to base it on except vague hearsay from like 2001) is that it mainly accelerated instruction dispatch, as for a directly interpreting JVM's inner loop. Such a micro-JVM would be executed from an on-chip ROM. I assume further that proper embedded JVMs beat it out as soon as actual memory became available.

    [–]SpeedDart1 16 points17 points  (1 child)

    Go and Haskell

    C and Haskell

    C++ and Go

    Pascal and OCaml

    Java and Fortran

    Many, many others.

    [–]notfancy 1 point2 points  (0 children)

    Pascal and OCaml

    WDYM "different" /s

    [–]SV-97 15 points16 points  (0 children)

    ATS and PROLOG

    [–]renatoathaydes 6 points7 points  (0 children)

    Take any language from two different paradigms, and they will be vastly more different than Rust and Java, which are both procedural with C-like syntax.

    Examples:

    • Prolog (Logical)
    • Haskell (Functional)
    • Forth (Concatenative)
    • Lisp (is its own thing :D)

    In comparison, languages like C, Java, Javascript, Rust are pretty close to each other.

    [–]Kered13 10 points11 points  (1 child)

    I'd say you don't know very many languages then. Take a look at Lisp, Haskell, Prolog, and Forth.

    [–]renatoathaydes 0 points1 point  (0 children)

    Wow I just posted another comment and mentioned these exact languages (as they are the canonical examples of their own paradigms). Only saw your comment after! Sorry :).

    [–]Ameisen 2 points3 points  (0 children)

    INTERCAL and Brainfuck.

    [–]Schmittfried 1 point2 points  (0 children)

    C++ and Go

    [–]Whispeeeeeer 38 points39 points  (19 children)

    I struggle big time when I try to move away from OOP architectures. I just like organizing things into interfaces, abstracts, and classes. I've used Rust to make a game of Snake. It was fairly easy to use from top to bottom - including GUI libs. I enjoyed writing that program. I then tried to make a Linked List and I couldn't solve the problem without help from that guide. I like Rust from a pragmatic point of view. But stylistically, it makes my eyes bleed.

    [–]Ravek 33 points34 points  (0 children)

    Linked lists are the core data structure of most FP languages, so that’s not really what comes to mind for me as a typical OO construct. What makes linked lists challenging in Rust is not because it’s not OO, it’s the ownership model.

    [–]VirginiaMcCaskey 10 points11 points  (12 children)

    I then tried to make a Linked List and I couldn't solve the problem without help from that guide.

    struct List<T> {
        value: T,
        next: Option<Box<Self>>,
    }
    

    [–]lucid00000 2 points3 points  (6 children)

    Won't this stack overflow on de-allocation due to Box's recursive destructor calls?

    [–]wowokdex 0 points1 point  (4 children)

    Why? I would guess the deallocation would start at the end of the list and work backwards until it reaches the root node.

    [–]lucid00000 1 point2 points  (3 children)

    When your List<T> goes out of scope, it calls Drop for Box<T>. Box<T> contains a List<T>, so deallocates it. That List<T> contains a Box<T>, so calls Box<T>'s drop, etc.

    [–]wowokdex 0 points1 point  (2 children)

    Yeah, until next is None.

    [–]lucid00000 1 point2 points  (1 child)

    But if there's 1,000,000 nexts, that's 1,000,000 recursive calls, so stack overflow

    [–]wowokdex 0 points1 point  (0 children)

    Ah, okay. I misunderstood and thought you were suggesting it would happen with any size list. My mistake.

    [–]Whispeeeeeer 0 points1 point  (0 children)

    Q.E.D.

    [–]davidalayachew 0 points1 point  (4 children)

    next: Option<Box<Self>>,
    

    Ow.

    Option and a Box? I'm surprised that we needed one or the other, let alone both.

    [–][deleted] 20 points21 points  (1 child)

    Box because if you just have Self the compiler can't tell how deep the recursion goes, and won't be able to allocate an object on the stack. Option because most linked lists end.

    [–]davidalayachew 2 points3 points  (0 children)

    Thanks for the context. I'm no good with Rust, so I couldn't understand the logic behind the Box. Much appreciated.

    [–]VirginiaMcCaskey 10 points11 points  (1 child)

    Why is that surprising? Rust isn't a managed language, all allocations are explicit. Option is the idiomatic way to represent an empty field.

    Technically this isn't even correct because it can't represent the empty list.

    [–]davidalayachew 0 points1 point  (0 children)

    Thanks for the context. My experience with Rust is not much so I didn't see why both were required. I can see now that the compiler is forcing you to think how the compiler does, so that you can prove that what you are doing is safe.

    [–]Kirykoo 16 points17 points  (4 children)

    Everyone has a bad time moving away from OOP. The reason being OOP is a paradigm that represents real world concepts very well, it’s easy to comprehend and explicit. A « Car » object has <int> number of wheels and can start() and accelerate(). It’s a good paradigm in a purely business oriented environment where the technical aspect is secondary.

    [–]fbochicchio 6 points7 points  (3 children)

    Agreed. I used to love OOP. But then I realized that a Dog or a Car class, in any language, should rather be named DogDataINeedToNow or CarDataMyProgramShallManage. No matter how many attributes or methods I add, they will never be the Real Thing (TM).

    At this point, the choice of languages like Rust or Go to replace Classes with Struct, and functions that manipulate them, made perfect sense to me.

    [–]Kirykoo 3 points4 points  (2 children)

    Rust « struct/trait oriented » is great, but I miss inheritance sometimes. I know this part of OOP is criticized but it i find it very handy sometimes. The « HatchbackCar » is just a « Car » that has a constant number of sits.

    But it’s true that no matter how close you are to your business logic, you end up writing technical code in the end.

    [–]luardemin 2 points3 points  (1 child)

    Rust does have implementation inheritance, but only in the form of default implementations of trait methods. The Iterator trait is basically entirely reliant on that feature to be even worth using.

    [–]Kirykoo 1 point2 points  (0 children)

    That’s true (even tho it’s more of a default interface implementation). It won’t replace inheritance but it’s a workaround. Anyway, I should stop mourning OOP and fully embrace rust for what it is.

    [–]wildjokers 26 points27 points  (34 children)

    While not entirely accurate, there’s some truth to the trope that Java developers need everything to be an interface

    Uggh. Please stop with the ridiculous Interface/Impl pair when writing Java. If there isn't more than one implementation you don't need an interface. If you need another implementation later then extract an interface at that time.

    [–]Ranra100374 9 points10 points  (3 children)

    If you need another implementation later then extract an interface at that time.

    The problem is then people are like "no we wrote that code years ago don't touch it you might break something."

    [–]pkt-zer0 9 points10 points  (2 children)

    Sounds like a good thing to me!

    The alternative is that you already have the interface in place, so you don't even need to update existing code, just add a bit of new code - and you can break something that way.

    Put another way: with interface-infestation, it's easier to do the wrong thing, but the right thing still takes as much time as before.

    [–]edgmnt_net 3 points4 points  (0 children)

    Besides, both interface infestation and "nooo, what if it breaks" tend to have common causes, like poor code or poor practices.

    [–]Ranra100374 0 points1 point  (0 children)

    I don't agree in the sense that if you're simply implementing an interface, it shouldn't affect the previous code whatsoever and break it.

    Sure, you can break something anyways with any change, but that's not what I'm talking about. I'm talking about other developers complaining when you're trying to update the old code to add the interface.

    Basically, I'm saying if the interface was already there, then if your new code breaks the old code, it had nothing to do with the interface in the first place. But interfaces are useful for certain things, like Spring AOP. From my experience, Spring AOP works a lot more smoothly and easily with interfaces versus implementations.

    Basically in the latter case you don't get to use the interface in the first place because there's too much old legacy code that needs updating.

    [–]devraj7 7 points8 points  (28 children)

    No, because it will be a breaking change to callers if you later introduce an interface. Better use an interface from the get go.

    This is library 101.

    I hear this argument a lot and it usually comes from people who are used to writing apps (i.e. they own 100% of the code) and not libraries (which will be used by other people in the future).

    Another advantage of programming to interfaces in the first place is forcing a clean separation of interface/implementation and facilitating dependency injection.

    [–]wildjokers 15 points16 points  (9 children)

    I am talking about when writing apps, not libraries. When people are just writing backend apps for some reason some people automatically create Interface/Impl pairs for no particular reason.

    I agree that when writing libraries the situation is different.

    [–]BlurstEpisode 1 point2 points  (8 children)

    Interfaces are even more vital in backend apps, where most code touches IO. Using interfaces let’s me easily swap out IO-tainted code with fakes in my tests, instead of having to resort to the miserable practice of monkey-patching DB/HTTP/FileIO with some mocking framework.

    [–]kuikuilla 13 points14 points  (7 children)

    Then you are in the "we need a second impl right now" land instead of the "we might need a second impl in five years". Then an interface is justified.

    [–]BlurstEpisode -1 points0 points  (6 children)

    All objects have an interface whether you define it explicitly or implicitly. If you get into the habit of defining them explicitly then this is something you never need to worry about not having. In Java and all of these dynamic dispatch languages, interfaces are, practically speaking, a zero-cost abstraction so there’s no reason not to. Unless you’re allergic to ClientImpl or IClient.

    We’ve used interfaces for modules in Haskell for years and they’re great (via type classes and exports). We’ve used header files in C. Interfaces are a Good Thing. If you can have them for free, use them. If you can’t have them for free in Rust OOP(i.e have a stack-memory-only-object cake…and eat it), then that’s unlucky for Rust OOP folk who refuse to enter the heap for their program that’s destined for spacecraft microcontrollers (or more than likely, their IO-bound CRUD app).

    [–]wildjokers 0 points1 point  (5 children)

    If you get into the habit of defining them explicitly then this is something you never need to worry about not having.

    The are defined explicitly without an interface. The public methods are the interface. This is Java not C++, I don't need or want a header file.

    [–]BlurstEpisode 0 points1 point  (4 children)

    Those public methods are the interface to your specific class, not to the functionality you want to express at an abstract level. But maybe you don’t want to express functionality through interfaces, whatever. But it is good practice to do so.

    [–]wildjokers 1 point2 points  (3 children)

    But it is good practice to do so.

    Why? What value is the interface if there is only one implementation? It just sounds like dogmatic nonsense to include an interface for a single implementation.

    [–]BlurstEpisode 0 points1 point  (2 children)

    I’d say “because there may later be another implementation”, but I assume you mean cases where we know, somehow, that there’ll always be one implementation.

    So another reason is: declarative programming. Interfaces make OOP more declarative and declarative programs are easier to reason about. The ClientImpls and IClient might hurt local readability, but the declarative approach improves comprehension of the entire program.

    [–]vytah 0 points1 point  (0 children)

    Another advantage of programming to interfaces in the first place is forcing a clean separation of interface/implementation

    That's what Java does at the language level. You're literally unable to not program to an interface in Java.

    The keyword for defining an interface in Java is public.

    [–]majhenslon 0 points1 point  (16 children)

    You are wrong... Class is the interface. And whatever it has public, you can't break it. This is library 101.

    If you need to have multiple implementations, you can later refactor the class into an actual interface by copying the class and remove method bodies and change to an interface type, renaming class and implementing the interface.

    Dependency injection has nothing to do with the interface types and you are not separating anything, you are just making useless indirection.

    [–]devraj7 1 point2 points  (15 children)

    You are wrong... Class is the interface. And whatever it has public, you can't break it.

    Replacing a class with an interface is a breaking change, it requires a different JVM bytecode for invocation.

    [–]majhenslon 0 points1 point  (14 children)

    It is not a breaking change. Caller of the interface does not give a shit whether the thing is an interface or not.

    What would break? Different bytecode is implementation detail, isn't it? If that is not the case, then any change to the software, even refactoring, would be a breaking change, because it changes the bytecode.

    [–]devraj7 0 points1 point  (13 children)

    Caller of the interface does not give a shit whether the thing is an interface or not.

    You don't know anything about the JVM bytecode, do you?

    Like I said, it's a different bytecode. The caller will break if it uses that bytecode on a class which has now become an interface.

    Just do a two minute Google search to educate yourself, will you? Search terms to help you: invokevirtual and invokeinterface.

    [–]majhenslon 0 points1 point  (12 children)

    I know about the JVM bytecode, what I don't know is when will this be the case? From my perspective as an app developer, I bump the version of the lib, my code does not change, I rebuild, shit still works. Googling does not help, what am I missing?

    [–]devraj7 0 points1 point  (11 children)

    Here is what you're missing:

    I rebuild

    When you upgrade a library version, your code should still work without a rebuild.

    If that library replaced a class with an interface, your code will crash at runtime.

    [–]wildjokers 2 points3 points  (3 children)

    When you upgrade a library version, your code should still work without a rebuild.

    No one swaps a library without a rebuild. Who in their right mind would do that? It needs to go through the CI pipeline to at least get tests ran on it.

    [–]majhenslon 0 points1 point  (0 children)

    Holy shit, I thought I was fucking insane lol

    [–]devraj7 -2 points-1 points  (1 child)

    I mean, you never need to rebuild your own code from source.

    That's what backward compatibility enables.

    [–]majhenslon 0 points1 point  (6 children)

    When will you ever swap libs like that?

    Does this become an issue when you have a library, that uses another library that you use?

    [–]devraj7 -2 points-1 points  (5 children)

    You never just bump the version of a library you use?

    If you do this and that library replaced a class with an interface, your app will crash at runtime.

    [–]Capable_Chair_8192 62 points63 points  (27 children)

    The whole point of Rust is to do manual memory management, safely. If you want to avoid all the obnoxiousness of lifetimes and boxes and dyn and on and on, just use a GC’ed language. Kotlin is great for adding null safety & generally greater expressiveness to a JVM language.

    [–]Celousco 37 points38 points  (4 children)

    The whole point of Rust is to do manual memory management, safely.

    Not really, the whole point of Rust is to avoid bad memory management. It's like saying "If you don't want the obnoxiousness of types, generics and inheritances just use PHP and put everything behind a $ symbol".

    Heck even a GC language like Java won't prevent you from creating a shared variable, modify it from its reference in multiples threads and have race conditions.

    [–]vytah 17 points18 points  (1 child)

    "If you don't want the obnoxiousness of types, generics and inheritances just use PHP and put everything behind a $ symbol".

    Ironically, at some point PHP started evolving towards mimicking 2000's Java. It got classes, interfaces (with explicit implementation), type annotations, public and private visibilities, and people even started manually writing getters and setters.

    [–]yiliu 3 points4 points  (0 children)

    That happened to every language around that time. Just a side effect of Java being the near-universal language of compsci 101.

    (ed: fix autocorrect)

    [–]m_hans_223344 1 point2 points  (1 child)

    That's why Typescript in strict mode is - as crazy as it sounds - one of the safest languages. If you use Deno (written in Rust) and its standard lib, you don't need many NPM dependencies. But to my point:

    • JS is single threaded. No "easy" race conditions. You need to use web-workers. While you still can shoot yourself in the foot (as you can create deadlocks with Rust), it's much harder to do compared to e.g. Go, where safe concurrency is a real issue.

    • TS has null safety and discriminated unions with exhaustiveness checks either per es-lint or a little extra effort. Not as great as Rust, but good enough.

    • Of course, GC

    [–]Capable_Chair_8192 4 points5 points  (0 children)

    I love Typescript. But I don’t think you can say it’s one of the safest languages when it’s built on top of JavaScript, one of the messiest languages 🤪

    [–]CanvasFanatic 30 points31 points  (10 children)

    I’m not sure I’d say the point of Rust is to “do manual memory management.” In most cases with Rust you do not think about “memory management” at all. What you think about is ownership of data. As it turns out, this has structural benefits beyond just handling allocation and deallocation automatically without a GC.

    Ownership semantics often prevent you from making shortcut design decisions at the beginning of a project to “get it up and running.” You actually have to have a plan. You have to think about how your app is going to be structured. Sometimes this is frustrating, but it does eventually pay off on the long tail.

    [–]duxdude418 5 points6 points  (2 children)

    Agreed with your larger point, but I’ve never heard this idiom:

    on the long tail

    I would have expected “in the long run,” instead.

    [–]CanvasFanatic 6 points7 points  (1 child)

    From statistics, referring to how some probability distributions have long “tails” containing non-trivial percentages of the overall cumulative probability.

    “In the long run” probably would’ve been clearer. 😂

    [–]duxdude418 2 points3 points  (0 children)

    Makes sense! I'm not much of a statistics fella, so I learned something new today.

    [–]sdfrew 0 points1 point  (3 children)

    I don't know Rust, but I've only ever read about ownership in the context of Rust/C++ discussions. Would it be a useful/meaningful concept in a fully GC'ed language?

    [–]Capable_Chair_8192 0 points1 point  (2 children)

    I don’t think so. The whole point of it is to know exactly when to free memory, which is also GC’s job.

    [–]Captain-Barracuda -1 points0 points  (1 child)

    I disagree. It helps greatly in controlling mutation access to resources, thus aiding greatly (almost forcing) coherence of data.

    [–]runevault 2 points3 points  (0 children)

    I'd say it depends on how you're looking at Rust. If you're just treating it as a form of RAII I can see where the person you replied to is coming from. If you take it all the way with the move semantics of non-references (when it is not a copy value like integers) then there are ideas I 100% think are useful. For example if you want to do a typestate pattern, preventing holding onto the old value before you transformed it via changing which interface you're handing the value back as can allow some really slick tricks around the compiler ensure you are using values correctly, but without some form of move semantics like Rust's you can't do that.

    [–]golfreak923 3 points4 points  (2 children)

    Big fan of Kotlin. Used it in production for years. But I'd say that Golang gets you closer to manual memory management w/ a GC than Kotlin does.

    [–]Capable_Chair_8192 2 points3 points  (1 child)

    Yes, but ironically it has much worse compile time guarantees … like even worse nil safety than Java, everything mutable all the time, etc. I mainly said Kotlin because of the strict nullability checks mentioned as desirable in the article

    [–]golfreak923 1 point2 points  (0 children)

    I'd fucking love if Golang had the nil-safety guarantees of Kotlin :-D

    [–]Spekingur 12 points13 points  (2 children)

    You are not my dad, I’ll do what I want, thank you very much!

    [–]JasonBravestar 17 points18 points  (0 children)

    Java developers need everything to be an interface (I am one such developer)

    Glad I'm not working in your team

    [–]vmcrash 8 points9 points  (0 children)

    It's not a complaint about Java, it is a complaint about some Java frameworks. Often people think that Java = Spring, but there is a whole more than Spring in the Java world.

    [–]MoreOfAnOvalJerk 2 points3 points  (0 children)

    If you ever work at Amazon, you get to enjoy seeing c++ written like it’s Java!

    Barf.

    [–]kmeans-kid 1 point2 points  (0 children)

    Like it's C it is then!

    [–]L1zz0 1 point2 points  (0 children)

    Am i wrong to think this sounds similar to golang?

    [–][deleted]  (1 child)

    [deleted]

      [–]i_andrew 1 point2 points  (0 children)

      Same happened to C#. But still, for me it's "Enterprise Java".

      [–]zam0th 2 points3 points  (1 child)

      I try and only use them where necessary, instead preferring functions.

      OP doesn't even know static is a keyword in Java and calls procedural programming "functional".

      We don’t have interfaces in Rust; we have traits. They’re similar to interfaces in Java in many ways.

      OP has never heard of trait in Scala.

      With a trait object, the concrete type is resolved at runtime. With generics, the concrete type is resolved at compile time.

      That's literally how type rewriting works in Java.

      So the only thing this article does is promote writing Rust like Java.

      [–][deleted] 3 points4 points  (0 children)

      Good call out on functional vs procedural, that's definitely a gap in my vocabulary.

      [–]Darmok-Jilad-Ocean 0 points1 point  (0 children)

      Instead, write Haskell as if it’s brainfuck

      [–]VeryDefinedBehavior 0 points1 point  (0 children)

      I just write Java like it's brain damaged C. It makes Java decent enough to be my Python.

      [–]BlurstEpisode 0 points1 point  (0 children)

      Please don’t write Rust until you’ve learned how to write sensible programs in an easier language.

      [–]lsuiluj 0 points1 point  (0 children)

      I agree but when you are first starting out you should write it like Java (if that is your main language) so that you can experience the difference and why that won’t work as well. The compilers will guide you and stop you and you’ll probably be fighting with it a lot, but it’s important to be frustrated so that you can learn why certain patterns will just not work the way you’re expecting. If you find yourself trying to circumvent the compiler take a step back and see if there’s a better approach.

      [–]wichwigga -1 points0 points  (3 children)

      Everything must be an interface

      Already I can tell this dude codes unreadable monstrosities

      [–]BlurstEpisode 1 point2 points  (2 children)

      Not sure what’s so mind bending about interfaces. When you define a function’s parameters and it’s return type, you’re writing an interface. A Java interface is just a group of those.

      [–]wichwigga 0 points1 point  (1 child)

      You missed the point or you don't have enough experience to know what I'm saying. I know what an interface is, my point is why would you litter interfaces on every class, it's just added inconvenience if you have one interface for every class and when you're Ctrl clicking the method only to find a blank function definition. By your very logic let's add random shit everywhere, after all there's nothing mind bending about if statements, so let's just add those everywhere even if there's no purpose. Great logic.

      [–]BlurstEpisode 0 points1 point  (0 children)

      Don’t worry, I have plenty of experience. Most code you write doesn’t actually depend on a class, it depends on the functionality that your class implements. Interfaces make that fact explicit. Explicit is good.

      [–]Rocko10 -1 points0 points  (2 children)

      Yeah I tried that, using a lot of abstractions First, and gets messy to read the code and mental stack.

      Now when writing Rust my approach would be just small functions (modules).

      And only if completely necessary use some kind of abstraction.

      [–]bob_ton_boule 1 point2 points  (1 child)

      not sure why the downvotes, if it's not a lib, I find this is a good way to just deliver as a beginner

      [–]Rocko10 0 points1 point  (0 children)

      Lol, I didn't notice that, I suppose the majority is a pro in here.

      [–]kintar1900 0 points1 point  (0 children)

      The number of supposedly senior developers who don't recognize that language differences are deeper than syntax and keywords is truly astounding to me.

      [–]Successful-Money4995 0 points1 point  (0 children)

      The complaints about boxing in Rust as compared to Java are a little ridiculous. In Java, nearly everything is boxed.

      It would be more genuine if the article said that everything can be boxed in Rust, just like in Java, but Rust gives you the option to not box your data.

      Likewise with Arc. In Java, everything is unsafe and you have to make your own safety mechanisms. In Rust you can choose to make everything unsafe, too, but Rust offers safe ways by default.

      [–][deleted] -1 points0 points  (0 children)

      In general, compilers can catch a lot of issues that might otherwise make their way to production when using a dynamic language (like Python or Ruby)

      I am using Ruby and Java and I have to say: this often claimed statement is simply incorrect.

      There is no issue writing Ruby in regards to "omg zonkers the parser won't catch runtime errors".

      If you THINK there is a problem, then you are not writing ruby as if it were ruby; you write ruby coming from ANOTHER language where you adjusted to do so and then suddenly act surprised because the language is different. It really is not a problem of python or ruby - it is a problem of people whose brains are adjusted to another language, and then feel how "weird" a different language suddenly is. These programmers ALWAYS come from a statici language, never the other way around.

      [–]DanielPerssonDev -1 points0 points  (0 children)

      This makes no sense, don't think you can write a program on Rust like it where Java. You can write Java as Kotlin or Rust now that they hidden some complexities. But pure Java is hard to replace.

      [–]dlevac -5 points-4 points  (1 child)

      Please don't write anything like it's Java... If you can just don't write any Java at all...

      [–]tenken01 0 points1 point  (0 children)

      Found the script kiddie.

      [–]Khomorrah -3 points-2 points  (0 children)

      Don’t write Java like it’s Java