Rust’s std::ops module provides a variety of traits for overloading Rust’s operators, but conspicuously skips the && and || operators. While I don’t believe these operators are that important to overload, I wanted to attempt to address one of the barriers to that functionality, namely short-circuiting, and provide a starting point for someone who actually cares to write an RFC.

As a refresher, short-circuiting is the behavior that these operators sometimes don’t evaluate their right-hand sides at all, depending on the value of their left-hand sides ( true || x is always true , false && x is always false ). This prevents representing these operators with function calls, because a functions’s arguments must be evaluated before it can be called. Even a closure isn’t enough - because an expression like lhs && return is valid, but cannot be transformed to and(lhs, || return) , which has different semantics.

A more involved approach is needed. Fundamentally, these short-circuiting operators evaluate the left-hand side and depending on that value either short-circuit to a result or evaluate the right-hand side and combine it with the left-hand side in an operation. To represent these two steps, our trait will need two methods, one to represent the short-circuit check and one for the actual operation. For the rest of this post, I will be speaking about the && operator; the || operator works pretty much the same.

trait And < Rhs = Self > { type Output ; fn and_short ( & self ) -> Option < Self :: Output > ; // alternate: fn and_short ( self ) -> Result < Self :: Output , Self > ; fn and ( self , rhs : Rhs ) -> Self :: Output ; }

In this sketch, and_short takes &self because if it returns None, we need to pass that same self to and . If we wanted to allow moving in and_short , its signature could instead be changed to the listed alternate, where Ok(v) is a short-circuited result and Err(s) returns the value to be used as the self value of and .

The desugaring of lhs && rhs then becomes fairly straightforward. To eliminate ambiguity, I’ve represented it as a macro:

macro_rules! and { ( $lhs:expr , $rhs:expr ) => {{ let lhs = $lhs ; match And :: and_short ( & lhs ) { Some ( value ) => value , None => And :: and ( lhs , $rhs ), } }} }

The left-hand operand is always evaluated immediately, then and_short is called. If it returns Some , the right-hand operand is not evaluted; if it returns None , the right-hand side is evaluated and and is called. Here’s how an implementation for bool might look, if we needed to implement it in Rust:

impl And for bool { type Output = bool ; fn and_short ( & self ) -> Option < bool > { match * self { false => Some ( false ), true => None , } } fn and ( self , rhs : bool ) -> bool { match ( self , rhs ) { ( true , true ) => true , _ => false , } } }

And here’s what an implementation for a hypothetical ternary logic value might look like:

enum Tri { False = - 1 , None = 0 , True = 1 , } impl And for Tri { type Output = Tri ; fn and_short ( & self ) -> Option < Tri > { match * self { Tri :: False => Some ( Tri :: False ), _ => None , } } fn and ( self , rhs : Tri ) -> Tri { match ( self , rhs ) { ( Tri :: False , _ ) | ( _ , Tri :: False ) => Tri :: False , ( Tri :: None , _ ) | ( _ , Tri :: None ) => Tri :: None , ( Tri :: True , Tri :: True ) => Tri :: True , } } }

Note that it’s important for consistency of behavior that for values of self where and_short returns Some(v) , and returns v for any value of rhs .