Failing with Class

Type classes as an abstrac­tion fea­ture is preva­lent in many mod­ern pro­gram­ming lan­guages. As dis­cussed in , class instances are not always sim­ple dec­la­ra­tions, but they can be poly­mor­phic, chain­ing class instances together. These poly­mor­phic instances breathe life to the power of type classes, but they also cre­ate a tower of abstrac­tion that, when fails, com­pil­ers can­not always reduce to a help­ful error diag­nos­tic.

For the scope of this work, a trait error has occurred if the lan­guage can­not prove that a type U imple­ments trait T. This def­i­n­i­tion does not state that U isn’t an imple­men­tor of T, rather, that its exis­tence can­not be proven.

We out­line our cri­te­ria for a good error diag­nos­tic. We call a diag­nos­tic good if it local­izes the root cause of the fail­ure and pro­vides full prove­nance for all required trait bounds. Poor diag­nos­tics fail to pro­vide this infor­ma­tion. We will show how branch­ing in the Rust trait solver impedes its abil­ity to local­ize the root cause of an error. Lan­guage design deci­sions impact which pat­terns are pre­ferred by library main­tain­ers. This sec­tion high­lights some com­mon pat­terns in Rust and how the inter­play between lan­guage fea­tures and trait res­o­lu­tion lead to poor diag­nos­tics.