Common newbie mistakes or bad practices

As in just create a from_str / from_whatever function? What's wrong with impl From?

I would actually appreciate if the compiler only spat out a single error message at a time by default. I can't fix multiple errors at the same time, so I will end up rebuilding for each one of them anyway.

5 Likes

Yeah. The practical problems with From with a single call-site are:

  • it's harder to find this call-site, it's not obvious what code exactly is called by ::from or .into
  • it's harder to refactor the code. If, in the future, you'll need an extra parameter at the call side, you'll need to first change the From to an inherent method.

The philosophical problem with From is that it signifies context-less conversion. The fact that there's a single place where you convert Foo into Bar doesn't mean that this conversion makes sense in general, it may only make sense in that particular context.

This is often the case with error in libraries. Let's say you have liba, which uses libb internally. liba can add impl From<libb::Error> for liba::Error to make implementing liba easier, because ? now works. However, that means that the user's code in libx can now do this:

fn doesnt_use_liba_at_all() -> Result<(), liba::Error> {
    libb::foo()?;
    libb::bar()?;
}

That is, the user can miss use From to construct liba::Error from libb errors which do not, in fact, originate in liba. Preventing this kinds of API hazards is one of the great ideas of snafu.

7 Likes

I am glad I am not the only person to struggle with From. The problem I run into is slightly different: auto completion:

Foo::from_blah(<TAB>
==> IntelliJ starts showing me argument names + types

Foo::from<TAB>
==> InteliJ shows me from_blah from_cat from_dog ...

Foo::from(<TAB>
==> IntelliJ can't complete for me, and I don't, from memory remember all the things we can From from

I reckon it’s reasonable when combined with #[cfg_attr].

Another place where it would be nice to use #[path] is with build script code generation:

#[path = concat!(env!("OUT_DIR"), "/generated.rs")]
mod generated;

Unfortunately, this still doesn’t work (it was initially said to be implemented in Rust 1.54, but it turned out not to be the case), so you have to write that like this:

mod generated {
    include!(concat!(env!("OUT_DIR"), "/generated.rs"));
}

I'm not sure if this belongs here or in some other place, but if novice in question is not a total novice then probably the most common mistake they do is an attempt to write JavaScript in Rust or Python in Rust or some other language in Rust.
Rust is usually presented as “multi-paradigm general-purpose programming language” which somehow convinces people who know some other mainstream language that they should be able to just write code in Java or C# and then, somehow, mechanically translate that into Rust.
Somehow C++ people rarely do that mistake, even if many C++ styles don't easily translate to Rust, too.
Rust maybe “multi-paradigm general-purpose programming language” but it's also incredibly opinionated and tries very hard to stir you toward “great APIs” (good APIs are easy to use, great APIs are hard to abuse).
Very often that make direct translation of other language to Rust either hard to do or impossible to do.
Besides: if you want to write JavaScript then why do you want to do that in Rust? JavaScript is perfectly viable language on it's own.

7 Likes

That deserves an answer:

Quite a lot of my Rust code looks like Javascript. Thing is I want to avoid as many as possible of Rust syntax features that are likely weird and alien to those readers of my code that don't know Rust, Javascripters and the like. Even if they don't have any Rust chops to be able to hack on my code I don't want to totally confuse them and frighten them away from Rust at first sight. So, for example if I have to use lifetime tick marks in my code I have failed.

Speed. Often I feel the need for speed. I can't make JS outrun compiled code.

See little example here: Writing Javascript in Rust ... almost

Scale. Javascript is really not suitable when programs get large and there are multiple people working on it. Rusts type checking helps enormously in keeping everything in order. It makes one far more confident when modifying/refactoring code knowing that Rust will prevent the myriad of ways that one can silently break things in other languages.

Politic: Before we had written a line of code our former employer threatened to sue us thinking we had stolen the code we wrote for them before they went bust. We had not. I thought it would be prudent to implement the similar functionality we needed in a different language, just in case. So what was JS and node.js became Rust.

On the other hand I write a lot of my Rust code as if it were C. As much as the compiler will allow. Because C like languages are what I understand.

Speaking of "mechanical translation". A vendor supplied us with a couple of thousand lines of C# to show how to communicate with their device. In lieu of documentation. I pretty much did a line by line translation of that in to Rust. Worked a treat.

3 Likes

I'd disagree with that. Rust gives us many tools with which one can solve problems using different approaches. You can write simple, linearly flowing procedural code. You can write "classic" OO-looking code with dynamic dispatch (although you'll be frowned upon by the community). You can encode sophisticated type-level constraint systems using traits and generics. You can write functional-looking recursive combinators, you can use iterators or plain old array indexing, and you can wrap your entire main function into an unsafe block and call out to C functions.

The only thing that Rust is really opinionated about is memory management. That's for the better, though – I don't really see any valid arguments against wanting to manage memory correctly, so I don't think that's a question of style or opinion.

6 Likes

See little example here: Writing Javascript in Rust ... almost

Saw. The old adage the determined Real Programmer can write FORTRAN programs in any language certainly is still valid. And I guess I agree: if, for some reason, you can't write Fortran in an actual Fortran then Rust is probably more suitable for that than JavaScript.

But let me remind a cite from the first Rust presenation: the syntax is, really, about the last concern.

When I was talking about JavaScript-in-Rust or Python-In-Rust I wasn't talking about attempts to mimic syntax of JavaScript or Python (some crates actually achieve that with help of macros). I was talking about capabilities of these languages.

How would you imitate change of the object class (JavaScript's prototype) after-the-fact in Rust? How would you do XML-driven dependency injection? How would you do something like C++ template meta programming?

All that is doable with macros and other such tricks, but code quickly become incomprehensible for both Rust pros and some-other-language pros. You, basically, need to know two languages and your glue code in addition.

3 Likes

Good question.

I would not.

On the other hand I have almost never changed object class or done XML-driven dependency injection in Javascript or template meta programming in C++. Despite having used those languages quite a lot.

All in all I'm not seeing Rust limit what I want to do.

1 Like

I would actually appreciate if the compiler only spat out a single error message at a time by default.

This. Especially since multiple errors can arise from a single source, and fixing one error can lead to another.

2 Likes

what's wrong with clap? It supports annotating input/config structs so you get a strongly typed parser. I'm a noob and don't understand. Is the community moving toward structopt?

1 Like

Clap's custom derive isn't out as a major release yet (the only released versions with it are clap 3.0.0-beta.2 to 3.0.0-beta.4), so I doubt many people know about it or have used it too much.

The structopt crate originally came up with the idea of using a custom derive to provide the ergonomics of writing declarative input/config with clap's excellent argument parser. I'm guessing over time they'll be merging a lot of structopt's functionality and lessons into clap itself.

4 Likes
  1. I wouldn't, as it's an artifact specific to Javascript, and not some goal to be achieved.
  2. I wouldn't, because XML for configuration is an antipattern (it isn't particularly human-friendly, and eg TOML does that much better). But even if we replace XML by TOML, I still wouldn't do it. Dependency injection is not something I actually want in my code, since it involves runtime reflection.
    In addition, I'd much rather be explicit about dependencies rather than having them injected¹.
  3. Rust has generics, so I'd just use that.

¹ I once had to read through a Java project that used dependency injection. Finding the actual classes that were injected (rather than the interfaces used in the injection APIs) was a true nightmare. Never again.

6 Likes

I'm glad you said all that. I was beginning to think it was just me that thought that way.

Actually, I'm trying to understand what "XML-driven dependency" injection is. A quick skim of Google tells me it's a Java thing. A fancy way of configuring a framework from XML whilst having all hope of knowing your control flow obfuscated. Sounds awful.

I have not thought deeply about dependecy injection, its really magical the first time I used it in a Spring Boot(Java based web framework blessed by the enterprises) application. Debugging wastes a lot of time in Java in general. I wish Rocket/Actix were as mature as Spring Boot(for enterprise apps) or Diesel as powerful as Hibernate, but it should be only a matter of time before ecosystem matures. :grinning:

Edit:

I believe what @VorfeedCanal said about XML driven dependency injection is mostly about the way things are done in Spring Boot. The framework uses XML/YAML/Properties files to configure itself. But dependency injection part doesn't really have to be tied to only configuration. In Spring Boot, the framework passes prepared objects for you in annotated methods and classes. Its like asking the framework for any specific component and you can start working with it without having to creating/managing instances manually. It would be nice if we could see something like this on Rust side(increases productivity, but less control over app) but I heard the tricks(Reflection) used to provide this are ugly under the hood.

After a superficial sniff around I still don't get it.

Seems one can write high-level Java code without regard for the lower level classes it may use. You just work to some interface. Then some XML config pulls in and sets up those classes for you at run time.

So one can swap out lower level implementations without recompiling code. Or at least changing any code. Just tweak the XML. Am I right?

Seems to just move things from Java code to XML.

What problem does XML-dependency injection solve?

1 Like

The idea is to decouple components (i.e. normal dependency injection) and use configuration to automate the task of constructing all the dependencies an object may have (and their dependencies, and so on.) without needing to recompile your code.

I'd argue that being able to change your code without touching your code is actually a negative because you can no longer just look at the Java source code and figure out what is going on. Needing to automate how you construct dependencies also feels like a code smell... if you have such a large/complex set of dependencies that you need to automate how they are constructed, then maybe you should try reducing complexity or coupling instead?

2 Likes

Not only this, but I'll argue that dependency injection, after the injection mechanism itself is accounted for, is actually more complex than providing the dependency objects manually.

So not only is it a code smell, it adds a big mountain of complexity and then papers over it by having the programmer only look at, and manipulate, the surface.

2 Likes

I believe you are right. DI may not be the most sound idea. But it is something that increases accessibility. I may have some bias here due to past experiences.

But we will have to take costs involved due to human labor into consideration as well. In the past, I have had to interview a lot of people and what I found truly scared me, it was the understanding that how few people can really write correct code. One strategy I found to get people be more productive is to decrease the cognitive bar for the people, this involves creating abstractions in form of frameworks/dsl to increase the number of hits for the people to correctly solve a problem. This really worked for me, I was able to remove entire months of training out of the window while keeping newly hired motivated(when they see results right from start). The bottom line was that accessibility really matters, it is the difference that will help deliver a product by translating human hours into productivity(which will open doors for their future growth and lead the project to standard and correct practices)

I consider DI to be in agreement with this bias of mine where I maximize for accessibility of technology for cognitively poor people to also contribute and be more productive. I believe technologies like JS and Java are popular only because of this aspect. Rust is also well positioned for this, macros increases accessibility.