4 ms·
let foo = "..."; let foo = parse(foo); let foo = escaped(foo); ... doSomethingWith(foo); I don't see how this is helpful for avoiding the bug d
by dkarl 8y ago
let foo = "...";
let foo = parse(foo);
let foo = escaped(foo);
...
doSomethingWith(foo);
I don't see how this is helpful for avoiding the bug described. The most common bug with this type of code is mistaking which form "foo" represents at a given line of code, or that form changing as the code evolves. For example, if one programmer writes
let foo = "...";
let foo = parse(foo);
...
doBarWithFoo(foo);
and another programmer comes along, doesn't notice the call to doBarWithFoo, and needs an escaped version of foo:
let foo = "...";
let foo = parse(foo);
...
let foo = escaped(foo);
doBazWithFoo(foo);
...
doBarWithFoo(foo); // This still looks correct in isolation
This is a classic problem with mutable variables that frequently causes hard-to-spot bugs. Whereas if each form has a distinct meaningful name, this change wouldn't introduce a bug, and if somehow a bug were introduced, good names will make it possible to spot even considering the line in isolation:
doBarWithFoo(fooEscaped); // Hey! The input to doBarWithFoo shouldn't be escaped!
If the only useful form of foo is the final one, then you can avoid having accessible names for the invalid intermediate forms like this:
let foo = escaped(parsed("..."));
Or like this for more complicated logic (I'm not a Rust programmer (yet) so this may not be the right syntax):
let foo = {
// Complicated logic
finalForm;
};
I feel like if I ever write a lot of code in Rust I'll find a linter rule that warns about shadowing variables and use it religiously. But maybe I'm missing a use case where it's crucial.
- wilg 8y agoI think the example only really works if `parse` and `escaped` return something other than a string. If that was the case, the type system will save you.
- moosingin3space 8y agoIn your particular example, the ownership system would help here, since `foo` is consumed by `doBazWithFoo`. (Only exception is when `foo` is `Copy`.)
- Misdicorl 8y agoI believe the way this is solved is by having the type of escaped(foo) different than that of parse(foo) and only accepting a EscapedString in doBaz and an ParsedString in doBar. Your type structure at no extra runtime cost is String : ParsedString : EscapedString. This ensures you don't escape strings before parsing them too. Nice!
- afraca 8y agoThis is common in Haskell as well using `newtype`. You can have type aliases like type A = B but you can use the following function with a B: f :: A -> _ whereas newtype EscapedString = EscapedString String f' :: EscapedString -> _ would prevent from using f' with the wrong data. Newtype is a zero-cost abstraction.