4 ms·
sorry to say this, but Rust version is much more difficult to read and comprehend than the C version. cannot comment on the benefits from memory-safety perspect
by larodi 5y ago
sorry to say this, but Rust version is much more difficult to read and comprehend than the C version. cannot comment on the benefits from memory-safety perspective, but from maintenance point of view the C version would be much easier to keep going.
my wild guess is that (as others pointed out) a C++ version may simplify and make things easier than Rust does with this example.
this said with all due respect to C, C++ and Rust and having been introduced to C back in 1993, and to C++ in 1995, and then teaching some C++ again in 2003-2005 and then some basic programs written in Rust.
code like this makes one really wonder whether Cxx14/17 programs are easier to get right/read than Rust, considering the massive amounts of template enums that inevitably finds way into every larger Rust program.
- apatheticonion 5y agoI agree. I like the ideas brought in by the borrow checker but I find Rust to be an unergonomic language to read. I would love to see something more like C, with a better type system, module import management, and a borrow checker. Think C + TypeScript
- vips7L 5y agoWhat about Zig? I think it checks everything off but a borrow checker.
- twic 5y ago> Rust version is much more difficult to read and comprehend than the C version By this, do you simply mean "I know C better than i know Rust"?
- larodi 5y agoPerhaps so, but then - also from the perspective of someone who easily reads/writes Perl, Python, ES6, complex PLSQL and templated C++. From my (personal) perspective something went in weird way with Rust borrower system, making it over-complex... although I have high regard for everyone who actually masters and uses it.
- dj_mc_merlin 5y agoHow specifically did you find the Rust version harder to grasp?
- diegocg 5y agoRust syntax isn't particularly friendly.
- magicalhippo 5y agoI'm not well versed in Rust, so that's obviously a factor. However the Rust version seems to have more line noise, like all the ".ok_or(Error::ENXIO)?;" bits that are sprinkled all over. Also compare the pl061_probe function and it's Rust equivalent. The C version is fairly straight forward and clean in terms of syntax, while the Rust function looks very complex in comparison. Also if you compare pl061_irq_ack with the Rust equivalent, suddenly the Rust version has an if statement in there while the C does not. What happens when the if statement is not taken?
- xondono 5y agoIn some part the problem here is that Rust is making error handling explicit, while the C driver has all those error handling covered in other files. For example, in the C version: struct pl061 *pl061 = gpiochip_get_data(gc); What happens if there's no chip? you should have a NULL check, but it's omitted because error handling is done outside of this file. The same is true for the additional if statement on the IRQ. In the end is a side effect of this being an example, because it should be possible to refactor the code where error handling is simply done through the ? operator. IMO that's a better way to do it than the C alternative, because right now there's no way to know if those struct pointers have proper error handling or not without leaving this file and hunting for each struct and function.
- moltonel3x 5y ago"opt.ok_or(err)?" is a pretty neat way of returning early with the error "err" if opt is empty. This is pretty idiomatic Rust you should get familiar with before comparing the readability with a language you know better. I see a few places where Rust is definitely more readable, for example for offset in 0..PL061_GPIO_NR { if inner.csave_regs.gpio_dir & bit(offset) != 0 { vs for (offset = 0; offset < PL061_GPIO_NR; offset++) { if (pl061->csave_regs.gpio_dir & (BIT(offset))) OTOH, the next line is a real mouthful in Rust compared to C: if let Ok(v) = <Self as gpio::Chip>::get(data, offset.into()) { inner.csave_regs.gpio_data |= (v as u8) << offset; } vs pl061->csave_regs.gpio_data |= pl061_get_value(&pl061->gc, offset) << offset; * "<Self as gpio::Chip>::" is the price Rust pays for having multiple possible "get()" for the same data, but it could be hidden away in a wrapper if needed. * ".into()" might be due to a young not-yet-ergonomic API * The last difference comes from the API difference where Rust's get() actually tells you if it could get that integer instead of (presumably) returning the integer 0. The C API is arguably a footgun, justifying Rust's slightly wordier syntax. For a more subjective example, I find "pl061.base.readb(GPIODIR)" more readable than "readb(pl061->base + GPIODIR)". Bonus: the offset argument can be enum-typed, avoiding the footgun of reading from an invalid offset. Going back to the probe functions, I find them hard to compare as I do not know how the hardware works. If that "device::Data::new()" is equivalent to the many lines of init in the C version, it looks less footguny. In the same vein, "Ref::try_new_and_init()" and "data.registrations()" look like they are giving the developer more guarantees. There's a trend there : some Rust APIs may be wordier but still easier to read/review, because they uphold more invariants, which reduces the reviewer's cognitive load. Concerning the ack() functions I'm not sure. It seems that Rust is checking that it has proper access to the data ressource while C doesn't. I would turn the question around : How are you sure that "gpiochip_get_data(irq_data_get_irq_chip_data(...))" never fails and can be safely dereferrenced ?