I’ll start with the really minor stuff.
Reconsider Names Like l
It’s easily confused with the numeral 1, or more rarely I, and if you don’t have l and r on the same screen, it can be very hard to guess what either one is an abbreviation for.
You Can Clean up zip_longest a Bit
The basic approach here is sound.  However, what you actually want to do is
- Take two generic iterators (so you can pass in reversed or mapped iterators)
- Whose item types have default values, from the std::default::Defaulttrait
- Pad the shorter sequence with default values (without cloning, although this is harmless for built-in types)
- Zip to an iterator over pairs
- And hand along the result to the chain
That gives you a function that you might plausibly re-use in another app.  It’s very close to what you wrote:
fn zip_pad<IterA, IterB>(mut left_it: IterA, mut right_it: IterB) ->
    impl Iterator<Item = (IterA::Item, IterB::Item)>
where
    IterA: Iterator,
    IterB: Iterator,
    IterA::Item: Default,
    IterB::Item: Default
{
    return std::iter::from_fn(move || {
        match (left_it.next(), right_it.next()) {
            (None, None)              => None,
            (Some(left), None)        => Some ((left, IterB::Item::default())),
            (None, Some(right))       => Some((IterA::Item::default(), right)),
            (Some(left), Some(right)) => Some((left, right))
        }
    })
}
The core logic is similar, but it drops IntoIterator, the user-supplied default value, and the separate Item type parameter (If that was on purpose, you might want a type alias), and is more flexible about accepting two different types of iterator.
Update: You’ve explained your reasoning in the comments, and it makes sense. Sometimes you want to pad with different elements (zero digits, spaces, null bytes).  In that case, I’d still clean up the associated types and traits a bit, but your approach better suits what you wanted and mine is more of a special case.
Now Some Significant Advice
Iterate Over the Bytes of the String, not the Codepoints
In Rust, a String is stored in UTF-8.  Calling String::chars will give you an iterator of 32-bit Unicode codepoints.  You can iterate forward or in reverse, but this is inefficient, compared to iterating over a Vec, because UTF-8 is a variable-width encoding.  You must count the bytes representing each Unicode codepoint and convert it to 32-bit UCS-4.
If you convert the string into an iterator of bytes, each element is a fixed width and the optimizer can go to town.  In particular, LLVM will now prrform tail-call elimination and inline all the functions.
Use bool for Internal Computation
The main convenience of this for you is that your matches can be exhaustive without needing to throw in a default clause (which spoils the very nice feature of warning you when you write a non-exhaustive pattern).  This also saves you from needing to do any runtime checks for valid char values after the initial conversion, and the compiler can pack them more efficiently than char—which, remember, is not a single byte in Rust, nor is a string stored as an array of char.
But it generates better code, too.
Here are some partial listings of what you get when running zip_longest on char iterators.  An excerpt of the code to check for None versus Some(l) and Some(r):
.LBB9_17:
        mov     rbx, r15
        cmp     ecx, 1114112
        jne     .LBB9_23
        jmp     .LBB9_46
.LBB9_11:
        movzx   edx, byte ptr [rbx - 1]
        test    dl, dl
        js      .LBB9_14
        dec     rbx
        cmp     ecx, 1114112
        jne     .LBB9_22
.LBB9_13:
        cmp     edx, 1114112
        jne     .LBB9_30
        jmp     .LBB9_46
There are at least six comparisons of a register to 1114112, AKA 0x110000, the first number that is not a valid Unicode codepoint and the binary representation of None for Option<char>.
This then calls carry_bits.  An excerpt of the code for the match expression in that function:
.LBB9_24:
        cmp     ecx, 48
        je      .LBB9_30
        cmp     ecx, 49
        jne     .LBB9_45
        cmp     edx, 49
        je      .LBB9_40
        cmp     edx, 48
        jne     .LBB9_49
        mov     cl, 49
        cmp     eax, 48
        jne     .LBB9_33
        jmp     .LBB9_39
.LBB9_30:
        cmp     edx, 48
        je      .LBB9_35
        cmp     edx, 49
        jne     .LBB9_49
        mov     cl, 49
        cmp     eax, 48
        je      .LBB9_39
.LBB9_33:
        cmp     eax, 49
        jne     .LBB9_49
        mov     cl, 48
        mov     ebp, 49
        jmp     .LBB9_43
.LBB9_35:
        mov     cl, 48
        cmp     eax, 48
        je      .LBB9_39
        cmp     eax, 49
        jne     .LBB9_49
        mov     cl, 49
        mov     ebp, 48
        jmp     .LBB9_43
.LBB9_39:
        mov     ebp, eax
ASCII 48 is '0' and ASCII 49 is '1', so we can see that the program is doing a series of nested conditional branches.  Unlike the previous set of conditional branches, which at least only switch to returning None once, these are not predictable.  This means they will incur hefty branch misprediction penalties on a modern CPU.
Calling the bool overload of zip_pad changes the generated code for one path through the same functions, adding two digits, to:
.LBB6_12:
        dec     qword ptr [rsp]
        cmp     al, 2
        jne     .LBB6_7
.LBB6_7:
        test    al, al
        setne   bl
        test    cl, cl
        setne   al
        mov     r14d, eax
        and     r14b, bl
        xor     bl, al
In this case, al holds an Option<bool>, whose binary representation is either 0 for Some(false), 1 for Some(true) or 2 for None.  If we call the Boolean value initially in al, a, and the value initially in cl, c, we see that, when a is not None, the program jumps to a branch that sets the next digit to a ⊕ c and the carry-out to a ∧ c.
The generated code for the more complex cases is:
.LBB6_35:
        dec     rdi
        cmp     al, 2
        jne     .LBB6_26
        test    r14b, r14b
        setne   bl
        jmp     .LBB6_37
.LBB6_25:
        xor     edx, edx
        mov     rdi, rsi
.LBB6_26:
        test    r14b, r14b
        setne   bl
        test    dl, dl
        setne   r12b
        test    al, al
        je      .LBB6_37
        mov     qword ptr [rsp], rdi
        test    dl, dl
        setne   al
        test    r14b, r14b
        setne   r12b
        or      dl, r14b
        setne   r14b
        xor     r12b, al
        xor     r12b, 1
        mov     r15b, 3
        lea     rbx, [rbp + 1]
        cmp     rbx, qword ptr [rsp + 8]
        jne     .LBB6_41
        jmp     .LBB6_42
.LBB6_37:
        mov     qword ptr [rsp], rdi
        mov     r14d, r12d
        and     r14b, bl
        xor     r12b, bl
        mov     r15b, 3
        lea     rbx, [rbp + 1]
        cmp     rbx, qword ptr [rsp + 8]
        jne     .LBB6_41
Not only are these shorter instructions, they are branchless, inline and use bitwise register instructions.  You also see that iterating over the byte iterator in reverse order, but not the String::chars() iterator, is as simple as decrementing a pointer by one byte.
You Can Generate the (Reversed) Output as an Iterator
It’s more efficient to do this and .collect() the results into a Vec than to insert within a loop, because each insertion must check the amount of space available and the current end of the vector, and resize if necessary.
When you .collect() a standard-library iterator, it comes with a size hint that lets the implementation create the Vec with approximately the right size buffer and fill it up to the amount of memory it reserved without checking again.
The code is very similar to the very nice code you wrote for zip_longest, which comes out fine when you call it with .bytes().rev().map(to_bool) instead of .chars().rev().
    let mut reverse_zip = zip_pad(a_rev, b_rev).peekable();
    let mut carry = false;
    let mut sum : Vec<u8> = std::iter::from_fn(move|| {
        match (reverse_zip.peek(), carry) {
            (None, false) => None,
            (None, true) => {
                carry = false;
                Some(true)
            }
            (Some(&(a, b)), carry_in) => {
                let (digit, carry_out) = add_bits(a, b, carry_in);
                carry = carry_out;
                reverse_zip.next();
                Some(digit)
            }
        }
    }).map(to_utf8)
      .collect();
There are a couple of wrinkles here: if there is a carry out of the final digit, we need to return one more digit of output than there were pairs of input digits.  If not for that, I could much more simply have used Iterator::scan.  Trying to cheat by not returning None on the last item of input causes an internal panic.
To work around that, I make the zipped iterator .peekable(), and then .peek() ahead to decide what to do.  If we’re on the last input item and there is no carry, I return the leading 1, clear the carry-in, and don’t advance the input iterator.  That causes the next iteration to hit the terminating case, safely.
Although it’s at odds with what is otherwise similar to functional programming, this kind of closure in Rust requires mutable external state.
You’ll also observe that this generates the output bytes in the same order as reverse_zip, that is, backwards.
Add from Right to Left, then Reverse, to Avoid Array-Shifting
I see that @cafce25 beat me to this one, but it’s important advice.  Whenever you insert at the front of a String, the entire string has to be shifted one character to the right, whenever you add a digit.  This takes O(N²) time in all.  You want to get that down to O(N).  Not only that, but a char in Rust is not an alias for byte like in C; it is a Unicode codepoint that must be converted from and two UTF-8 whenever it is extracted from and inserted into a String.
Remove Unnecessary Copies
Once you have generated the digits of output into a sequence of bool, you can convert it to ASCII digits by mapping a helper function:
    const fn to_utf8(d: bool) -> u8 {
        match d {
            false => '0' as u8,
            true  => '1' as u8
        }
    }
This compiles to next-to-no overhead: an inline add r8, 48 instruction (since 48 is '0', 49 is '1', 0 is false and 1 ia true).  The code above collects all of these converted ASCII digits into a mutable Vec<u8>.
Why mutable?  There’s a function sum.reverse() that reverses the bytes of output in place, with no additional memory allocation or copying.  When you enable vector instructions, the implementation uses them to optimize this step.
Since that gets us the correct UTF-8 output string, we can call String::from_utf8 to move the buffer directly from the Vec container to a String container, without needing to make another copy.  Since this returns a Result type, we need to .unwrap() the return value.  It’s good practice to use .expect for this with a debug message. (We could in theory use the unsafe function String::from_utf8_unchecked instead.)
Putting it All Together
fn zip_pad<IterA, IterB>(mut left_it: IterA, mut right_it: IterB) ->
    impl Iterator<Item = (IterA::Item, IterB::Item)>
where
    IterA: Iterator,
    IterB: Iterator,
    IterA::Item: Default,
    IterB::Item: Default
{
    return std::iter::from_fn(move || {
        match (left_it.next(), right_it.next()) {
            (None, None)              => None,
            (Some(left), None)        => Some ((left, IterB::Item::default())),
            (None, Some(right))       => Some((IterA::Item::default(), right)),
            (Some(left), Some(right)) => Some((left, right))
        }
    })
}
fn ripple_carry_adder( left_input :&str, right_input: &str ) -> String {
    let a_rev = left_input.bytes()
                          .rev()
                          .map(to_bool);
    let b_rev = right_input.bytes()
                           .rev()
                           .map(to_bool);
    let mut reverse_zip = zip_pad(a_rev, b_rev).peekable();
    let mut carry = false;
    let mut sum : Vec<u8> = std::iter::from_fn(move|| {
        match (reverse_zip.peek(), carry) {
            (None, false) => None,
            (None, true) => {
                carry = false;
                Some(true)
            }
            (Some(&(a, b)), carry_in) => {
                let (digit, carry_out) = add_bits(a, b, carry_in);
                carry = carry_out;
                reverse_zip.next();
                Some(digit)
            }
        }
    }).map(to_utf8)
      .collect();
    /* The output string was generated in reverse order.  Reverse it in place,
     * without copying.
     */
    sum.reverse();
    /* Since the bytes of the vector are valid UTF-8, move them to a string,
     * without copying.
     */
    return String::from_utf8(sum).expect("The output should have contained only two possible values.");
    const fn to_bool(c :u8) -> bool {
        match c as char {
            '0' => false,
            '1' => true,
            _ => unimplemented!()
        }
    }
    const fn add_bits(a: bool, b: bool, c: bool) -> (bool, bool)
    {
        match (a, b, c) {
            (false, false, false) => (false, false),
            (false, false, true)  => (true, false),
            (false, true, false)  => (true, false),
            (false, true, true)   => (false, true),
            (true, false, false)  => (true, false),
            (true, false, true)   => (false, true),
            (true, true, false)   => (false, true),
            (true, true, true)    => (true, true)
        }
    }
    const fn to_utf8(d: bool) -> u8 {
        match d {
            false => '0' as u8,
            true  => '1' as u8
        }
    }
}
And a few tests:
pub fn main() {
    let test1 = ripple_carry_adder("101", "1");
    assert!(test1 == "110", "Expected 101 + 1 = 110. Got {}", test1);
    println!("Test 1 passed.");
    let test2 = ripple_carry_adder("1011", "1");
    assert!(test2 == "1100", "Expected 1011 + 1 = 1100. Got {}", test2);
    println!("Test 2 passed.");
    let test3 = ripple_carry_adder("101010", "10101");
    assert!(test3 == "111111", "Expected 101010 + 10101 = 111111. Got {}", test3);
    println!("Test 3 passed.");
    let test4 = ripple_carry_adder("100", "100");
    assert!(test4 == "1000", "Expected 100 + 100 = 1000. Got {}", test4);
    println!("Test 4 passed.");
}
You can try it out on Godbolt.