He knows Rust well but software design not so much. I know software design well but Rust not so much. My experience can be summed up with:
let &mut writer = Writer<T>::writer::new(connection.clone()).clone(); // TODO ??? writer.write(*data.clone()).unwrap();
Meanwhile in C++ I'm like
if (!write(connection, data, data_len)) { return false; }
let &mut -- Essential keywords.
writer = -- Duh
Writer<T> -- Generic types. Important.
::writer::new -- Boilerplate.
(connection.clone()).clone; -- Relentless cloning is a big problem.
...
.unwrap -- "Consistent names for optional types," is an issue in Rust. Every module has different jargon for Some(x).
Rust certainly isn't perfect. The borrow checker creates... awkwardness, that requires .clone hacks to solve.Flip-side is, I'm coding in CMake right now. I've had to create multiple bash scripts for manually deleting my build files and general day to day.
Software is a young profession.
Everything is shit.
clone() itself makes perfect sense. Why the programmer needed those clones... is a legitimate issue with Rust.
In my experience so far, it's usually to hack around an interaction between the borrow checker and the output of a function constructed using dot syntax.
A normal writer API would look like one of the variations:
Writer<T>::new(&connection).write(&data)
Writer<T>::new(&mut connection).write(&data) // if conn needs changes.
It's rare to unwrap(), which can cause a crash at runtime, but to handle the result. E.g. to write more data if the previous write is successful. let mut writer = Writer<T>::new(&connection);
let mut written_len = 0;
written_len += writer.write(&data1)? // ? returns the err if it's Err
written_len += writer.write(&data2)? // or unwrap the result value
written_len += writer.write(&data3)?
As you can see, "writer.write(&data1)?" is equivalent to the C++ version.The actual code we were working on involved functions returning closures with mutable captures, so the borrow checker was especially persnickety.
> functions returning closures with mutable captures
That sounds like another bad design, but to each his own.
It's not difficult to arrive at the caricature of baroque generic code if you combine lack of knowledge with miscommunication. The knowledgeable coworker should be aware that a writable object implements the Write trait, and know, or find out, the signature of the Write::write() method. Even fully generic, a function accepting a connection and returning the result of writing a block of data is not too gnarly:
use std::io::{self, Write};
fn write_data<W: Write, D: AsRef<[u8]>>(connection: &mut W, data: D) -> io::Result<usize> {
connection.write(data.as_ref())
}
No unwraps, no clones. Because they're not necessary here. But someone has to know the language and the idioms. Even your "easy" C++ code depends on knowing that write() returns zero on success, and that integers can play the role of booleans in conditions.*I totally agree with you that folk writing code need to understand the semantics of what they're building on top of. Humans and now AI models have a great capacity for generating tons source code without understanding the semantics, though.
connection.write_all(data)
Where data could be an argument that impls ToOwned or Into so that it would either use the object you pass in (if by value) or implicitly clone it (if by reference).Basically this looks more like an API design problem than a limitation of the language.