this post was submitted on 28 Mar 2024
242 points (94.2% liked)

Rust

5960 readers
2 users here now

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

[email protected]

Credits

  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

founded 1 year ago
MODERATORS
 

Slide with text: “Rust teams at Google are as productive as ones using Go, and more than twice as productive as teams using C++.”

In small print it says the data is collected over 2022 and 2023.

you are viewing a single comment's thread
view the rest of the comments
[–] [email protected] 30 points 7 months ago* (last edited 7 months ago) (5 children)

I don't know. After writing rust for a while, and slowly putting programs together, I tried Go and I feel so relived I can just write what I want in 10 seconds instead of messing with lifetimes, borrow checker and other stuff I actually don't care about at all.

A more experienced colleague said that yes that is true, but Go can't guarantee your code is correct, so you will spend time fixing your code also in Go. Probably true.

[–] [email protected] 40 points 7 months ago (2 children)

Right, it's essentially the same argument as strong vs. weak typing. The weak typing proponents say JavaScript is best, because you can just write anything and you don't need to worry about all those pesky types getting in your way. The strong typing proponents (which if it's not obvious I am one of) point out that you can write incorrect code quickly in just about any language, but writing correct code is much harder, and the cost of correcting code increases the later the mistake is found. Errors that can't even be written are better than errors that are found at compile time which are better than errors that are reliably caught at runtime, which are all infinitely better than errors that only randomly appear under very specific circumstances.

That is why many people switched to using TypeScript for their websites instead of JavaScript, because even though you have to spend more time putting type annotations on everything, and at the end of the day at runtime TypeScript is literally just JavaScript, the errors it lets you find at compile time instead of runtime make the effort necessary to include those types worth it. Same thing applies with Rust vs. Go. Yes it requires more thinking up front when you're writing Rust code, and yes it might take you longer to write that code, but it's also going to be correct code you can be confident in and not have a bunch of ticking timebombs waiting in it that you don't even know about.

An extra 30 minutes spent having to think about a dozen lines of code, is infinitely preferable to spending 3 hours pouring over stack traces and single stepping debuggers to find that one subtle mistake you made.

[–] [email protected] 11 points 7 months ago (3 children)

I totally agree, though I think it's worth adding:

  • The advantages of static types is not just finding bugs (though it does do that quite well). It also massively helps with productivity because a) types are now documented, b) you can use code intelligence tools like renaming variables, go-to-definition, find-references, etc. (assuming you use a good editor/IDE).

  • In general stronger types are better but I do think there is a point at which the effort of getting the types right is too high to be worth the benefit. I would say Rust hasn't reached that point, but if you look at formal verification languages like Dafny, it's pretty clear that you wouldn't want to use that except in extreme circumstances. Similarly I think the ability to use an any or dynamic escape hatch is quite useful, even if it should be used very sparingly.

[–] [email protected] 4 points 7 months ago (1 children)

You are right. But I think similar secondary benefits also come from using the borrow checker. Rust developers, by necessity, try to avoid using circular references and prefer immutability where they can. Both of these are advantages because they tend to make for systems that are easier to understand and are easier to maintain.

[–] [email protected] 1 points 7 months ago

Yeah I agree. The borrow checker definitely pushes you to write less buggy code.

[–] [email protected] 3 points 7 months ago

It also massively helps with productivity

Absolutely! Types are as much about providing the programmer with information as they are the compiler. A well typed and designed API conveys so much useful information. It's why it's mildly infuriating when I see functions that look like something from C where you'll see like:

pub fn draw_circle(x: i8, y: i8, red: u8, green, u8, blue: u8, r: u8) -> bool {

rather than a better strongly typed version like:

type Point = Vec2<i8>;
type Color = Vec3<u8>;
type Radius = NonZero<u8>;
pub fn draw_circle(point: Point, color: Color, r: Radius) -> Result<()> {

Similarly I think the ability to use an any or dynamic escape hatch is quite useful, even if it should be used very sparingly.

I disagree with this, I don't think those are ever necessary assuming a powerful enough type system. Function arguments should always have a defined type, even if it's using dynamic dispatch. If you just want to not have to specify the type on a local, let bindings where you don't explicitly define the type are fine, but even in that case it still has a type, you're just letting the compiler derive it for you (and if it can't it will error).

[–] [email protected] 2 points 7 months ago* (last edited 7 months ago) (1 children)

You can go to definition / find references / rename for dynamically typed languages too.

E.g. https://github.com/palantir/python-language-server

[–] [email protected] 5 points 7 months ago (1 children)

Without static type annotations you can only make best effort guesses that are sometimes right. Better than nothing but not remotely the same as actual static types. The LSP you linked works best when you use static type annotations.

Also I would really recommend Pylance over that if you can - it's much better but is also closed source unfortunately.

[–] [email protected] 2 points 7 months ago (3 children)

Why would it just be best effort? To find references for a specific thing, it still would parse an AST, find the current scope, see it's imported from some module, find other imports of the module, etc.

[–] [email protected] 5 points 7 months ago* (last edited 7 months ago) (2 children)
if random() > 0.5:
    x = 2
else:
    x = "hello"

Where is the definition of x? What is the type of x? If you can't identify it, neither can the LSP.

This kind of thing actually happens when implementing interfaces, inheritance, etc. Thus, LSPs in dynamic languages are best effort both theoretically and in practice.

[–] [email protected] 1 points 7 months ago* (last edited 7 months ago) (1 children)
  1. Look at entire file instead of snippet.
  2. If there is anything that could create a variable x before this area, then that's where x originates. If not, and if it's a language where you can create x without using a keyword like let or var, then x is created in the scope in your snippet.

Types are not necessary at all.

[–] [email protected] 2 points 7 months ago (1 children)

then x is created in the scope in your snippet

Saying "x is defined somewhere in the entire program" isn't satisfactory to many users. Also, you didn't tell me what type x has. Can I do x + 5?

[–] [email protected] 1 points 7 months ago (1 children)
  1. That isn't what I said at all. Reread?
  2. Find references / go to definition / rename has absolutely nothing to do with types.
[–] [email protected] 1 points 7 months ago (2 children)

Find references / go to definition / rename has absolutely nothing to do with types.

It absolutely does. Without static types an IDE/LSP can't reliably find all the references / definition and therefore can't refactor reliably either.

Consider something like this:

class Foo:
  bar: int

class Baz:
  bar: str

def a(f: Foo) -> int:
  return f.bar + 1

def b(f: Baz) -> str:
  return f.bar + "1"

Now imagine you want to rename Foo.bar or find all references to it. Impossible without the type annotations.

[–] [email protected] 1 points 7 months ago

Ah, I see. You're talking about object properties. I don't see any issue with finding references to variables, but for properties, yeah.

[–] [email protected] 1 points 7 months ago (1 children)

Tbf this example can be deducted as string | int just fine.

[–] [email protected] 1 points 7 months ago

The real problem is when you start using runtime reflection, like getattr(obj, "x")

[–] [email protected] 1 points 7 months ago (1 children)
def get_price(x):
   return x.prize

Ok imagine you are a LSP. What type is x? Is prize a typo? What auto-complete options would you return for x.?

[–] [email protected] -1 points 7 months ago (1 children)

I didn't say types. I said find references / go to definition / rename.

[–] [email protected] 2 points 7 months ago

How are you going to find references to prize, go to its definition or rename it without knowing what type x is? It's impossible without static types.

[–] [email protected] 1 points 7 months ago

It breaks down when you do runtime reflection, like getattr(obj, "x").

[–] [email protected] 7 points 7 months ago
[–] [email protected] 13 points 7 months ago* (last edited 7 months ago) (2 children)

instead of messing with lifetimes, borrow checker and other stuff I actually don’t care about at all

There's nothing wrong with putting Rc<_> or Rc<RefCell<_>> around data if you don't want to fight the borrow checker or think about lifetimes even if you know it can be written without.

[–] [email protected] 4 points 7 months ago

Or even just clone. Depending on use case the performance cost would be negligible.

[–] [email protected] 4 points 7 months ago (1 children)

There's nothing wrong with putting Rc<> or Rc<RefCell<>> around data

It's mainly the visual pollution that bothers me. Wrapping everything in the reference counting smart pointers just because you can't be bothered dealing with the borrow checker seems like an antipattern

[–] [email protected] 3 points 7 months ago

I don't know why so many recommend Rc or Arc as a catchall. 90% of the time if you want to avoid the borrow checker then a clone or copy is good enough.

[–] [email protected] 11 points 7 months ago

Really? I might have agreed for some other languages, but Go is so bare bones it feels like it takes way longer to write simple stuff than with Rust - you have to tediously write out loops all the time for example.

Tbf I haven't used it since it got generics. Maybe it is better now.

[–] [email protected] 1 points 7 months ago (1 children)

#Rust is not high level at all, change my mind.

[–] [email protected] 3 points 7 months ago

Rust gives you high level abstractions but also allows lower level control over the hardware. These are not mutually exclusive.

You can easily argue almost any language is high level though, it is such a nebulous term that it is almost meaningless.