Multiplicity Choices Are Hard to Model and Change

  • I broadly agree with multiplicity problems being the worst, but have some nits.

    > because my language's static type system probably won't give me any leads on what needs fixing

    This is because moving to Vec<Address> introduced 0 addresses as a possibility into the type already and you didn't deal with it then. If the author had moved to NonEmptyVec<Address> first, then you could have transitioned to Option<NonEmptyVec<Address>>.

    > Ironically, I often find that the hardest places to work out what to do are those where the static type system forces me to deal with the possibility of there not being a T but my analysis of the code shows (or, at least, suggests) that at run-time there always will be a T. This leads to a counter-intuitive situation: although the static type system has helpfully told me all the places where changing the multiplicity needs to be considered, I can introduce more run-time errors than I could ever have encountered in a non-option-types language!

    This doesn't quite make sense. Either the errors could also happen in the dynamic type setting in which case you were missing checking you needed, or they can't happen in either setting in which case you may have unnecessary code checking for a missing T, but those runtime errors never actually occur in practice. I think maybe the author means those unnecessary checks are "more runtime errors", but it's not that you actually get more errors in practice, it's just that someone reading the code might think there is the possibility of errors in more places than there actually are. There is an easy solution to this though: find the earliest point where it's guaranteed that there is actually a T, put one assert there with a comment indicating that if this assert is ever false it's a bug, then stuff the the unwrapped T in a new variable and use it from then on. If you need to pass it to a function that requires an option just call it with f(Some(foo)).

    > (e.g. do I dynamically check the property each time I create one of my new wrapper types? what is the performance cost?)

    No you only have a constructor that requires you to pass in at least one initial element which will statically enforce that it's at least size one, and then only have the container assert that it's still at least size one when methods that remove elements are called (which in most use cases you probably won't ever call or will call rarely). The cost is one branch when erasing, it's very cheap. And since erasing from anywhere in the vector except the end involves shifting comparatively speaking the branch is cheap.