I think C and its direct descendants will slowly fade away over the next 20 years as new developers want to get away from the legacy of language specifications that span existing codebases. There are so many sharp edges in C that have automatic fixes/detections/etc in newer languages and don’t get me started about multiprocessing complexity in C.
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.
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.
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.* 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.