Hex string to Base64 The base64 decode algorithm is taken directly from Base64 wiki page. This exercise is meant to help me learn Rust, so any pointers in that general direction are very welcome. :)

fn main() {
    println!("{}",
         hex_to_base64("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"));
}

fn hex_to_base64(hex_string: &amp;str) -&gt; String {
    println!("Converting {} from hex to base64", hex_string);
    let alphabet: Vec&lt;char&gt; =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".chars().collect();

    // Decode the input string from hex into individual bytes
    let input_chars: Vec&lt;char&gt; = hex_string.chars().collect();
    let mut decoded_bytes: Vec&lt;u8&gt; = Vec::new();

    for i in 0..hex_string.len() {
        if i % 2 != 0 { continue }
        let first_byte = input_chars[i].to_digit(16).unwrap() as u8;
        let second_byte = input_chars[i + 1].to_digit(16).unwrap() as u8;
        println!("Decoding 0x{}{} to bytes:", input_chars[i], input_chars[i + 1]);
        let byte = (first_byte &lt;&lt; 4) | second_byte;
        println!("    {}", byte);
        decoded_bytes.push(byte)
    }

    // Encode the decoded bytes into Base64
    let mut output = String::from("");

    let mut b: u8;
    for i in 0..decoded_bytes.len() {
        if i % 3 != 0 { continue }
        b = (decoded_bytes[i] &amp; 0xFC) &gt;&gt; 2;
        output.push(alphabet[b as usize]);
        b = (decoded_bytes[i] &amp; 0x03) &lt;&lt; 4;
        if i + 1 &lt; decoded_bytes.len() {
            b |= (decoded_bytes[i + 1] &amp; 0xF0) &gt;&gt; 4;
            output.push(alphabet[b as usize]);
            b = (decoded_bytes[i + 1] &amp; 0x0F) &lt;&lt; 2;
            if i + 2 &lt; decoded_bytes.len() {
                b |= (decoded_bytes[i + 2] &amp; 0xC0) &gt;&gt; 6;
                output.push(alphabet[b as usize]);
                b = decoded_bytes[i + 2] &amp; 0x3F;
                output.push(alphabet[b as usize]);
            } else {
                output.push(alphabet[b as usize]);
                output.push('=');
            }
        } else {
            output.push(alphabet[b as usize]);
            output.push_str("==");
        }
    }
    return output
}

#[test]
fn test_hex_to_base64() {
    assert!(hex_to_base64("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d") == String::from("SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t"));
}
I am glad to see your method takes a &amp;str instead of a &amp;String or String!
Shouldn't be printing output inside function.
The one function is doing too much. Instead, create two functions, one to convert hex -> bytes, andone to convert bytes -> base64. Note that your comments already declare where functions should be! You could promote those to doc comments, or the function name may be enough.
Don't specify exact types when declaring variables, let type inference handle it. decoded_bytes: Vec&lt;u8&gt; = Vec::new(); should be decoded_bytes = Vec::new();. Note that changed code has very few foo: type lines, mostly for collect where the return type is up to the caller.
Can collect into a Vec&lt;_&gt;, no need to specify item type.
Use chunks instead of reimplementing it. Note that it returns a smaller chunk when not a multiple of the chunk size. hex_to_bytes will crash with odd count of values, I think the previous did as well.
Once operating on an iterator, switch to map and add collect. Avoids need for mutation.
Prefer expect over unwrap. When it crashes you will be thankful.
String::new instead of String::from("").
Traditionally, the Base64 conversion table is a hard-coded constant, not re-created for each call.
The length of the bytes never changes, get it once before the loop and reuse it.
Declare variables as near to usage as they can be. b shouldn't be outside the loop.
Consider rebinding b when it's reset to a brand new value.
Consider getting the result of indexing and storing it into a variable, since it shouldnt change
Instead of checking the length and then indexing, use if let combined with get.
Use chunks again.
No explicit return at end of method.
Use assert_eq!.
Can compare string literal against String, no need to allocate





  
  Is there a better way to write debug statements?
  


You may be interested in the log crate, although I'd argue that these printing statements only had any value when the function was being originally developed, and probably don't deserve to stay in as logging either.


  
  I'm not sure what this one means?
  


Instead of

let mut b = 1;
b += 1;
b = 5;


You may want to try

let mut b = 1;
b += 1;
let b = 5;


This may help indicate that you deliberately reset the value.



fn main() {
    let b64 = hex_to_base64("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d");
    println!("{}", b64);
}

/// Decode the input string from hex into individual bytes
fn hex_to_bytes(hex_string: &amp;str) -&gt; Vec&lt;u8&gt; {
    let input_chars: Vec&lt;_&gt; = hex_string.chars().collect();

    input_chars.chunks(2).map(|chunk| {
        let first_byte = chunk[0].to_digit(16).unwrap();
        let second_byte = chunk[1].to_digit(16).unwrap();
        ((first_byte &lt;&lt; 4) | second_byte) as u8
    }).collect()
}

/// Encode the decoded bytes into Base64
fn bytes_to_base64(decoded_bytes: &amp;[u8]) -&gt; String {
    let mut output = String::new();
    let alphabet: Vec&lt;_&gt; =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".chars().collect();

    for chunk in decoded_bytes.chunks(3) {
        let c0 = chunk[0];
        let b = (c0 &amp; 0xFC) &gt;&gt; 2;
        output.push(alphabet[b as usize]);

        let mut b = (c0 &amp; 0x03) &lt;&lt; 4;

        if let Some(c1) = chunk.get(1) {
            b |= (c1 &amp; 0xF0) &gt;&gt; 4;
            output.push(alphabet[b as usize]);

            let mut b = (c1 &amp; 0x0F) &lt;&lt; 2;

            if let Some(c2) = chunk.get(2) {
                b |= (c2 &amp; 0xC0) &gt;&gt; 6;
                output.push(alphabet[b as usize]);

                let b = c2 &amp; 0x3F;
                output.push(alphabet[b as usize]);
            } else {
                output.push(alphabet[b as usize]);
                output.push('=');
            }
        } else {
            output.push(alphabet[b as usize]);
            output.push_str("==");
        }
    }

    output
}


fn hex_to_base64(hex_string: &amp;str) -&gt; String {
    bytes_to_base64(&amp;hex_to_bytes(hex_string))
}

#[test]
fn test_hex_to_base64() {
    assert_eq!(
        hex_to_base64("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"),
        "SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t"
    );
}