Can't capture dynamic environment in a fn item when pass function arg to macro pattern

I have a macro for generating structs and I want to use this macro inside function to pass args into it:

fn test(opcode: u32) -> Vec<u8> {
    packet! {
        @option[opcode=opcode]
        struct Income {
            name: String,
            age: u64,
        }
    }
    
    Income { name: String::from("Name"), age: 12345 }.to_binary()
}

but when I try to compile I got an error:

error[E0434]: can't capture dynamic environment in a fn item
   --> src/main.rs:189:24
    |
189 |         @option[opcode=opcode]
    |                        ^^^^^^
    |
    = help: use the `|| { ... }` closure form instead

so I tried to follow the hint and refactor the code into:

fn test(opcode: u32) -> Vec<u8> {
    packet! {
        @option[opcode=|| opcode]
        struct Income {
            name: String,
            age: u64,
        }
    }
    
    Income { name: String::from("Name"), age: 12345 }.to_binary()
}

but error still there.

This is full code of my macro:

#[macro_export]
macro_rules! packet {
    (
        $(@option[opcode=$opcode_value:expr])?
        $(@option[no_size:$no_size:expr])?
        $(@option[compressed:$compressed_value:expr])?

        $(#[$outer:meta])*
        $vis:vis struct $PacketStruct:ident {
            $($field_name:ident: $field_type:ty),*$(,)?
        }

        $($PacketStructImpl: item)*
    ) => {
        $(#[$outer])*
        #[derive(Clone, Debug, Default)]
        $vis struct $PacketStruct {
            $($field_name: $field_type),*
        }

        $($PacketStructImpl)*

        impl $PacketStruct {
            // income
            pub fn from_binary(buffer: &Vec<u8>) -> Self {
                let mut omit_bytes: usize = INCOMING_HEADER_LENGTH;
                $(
                    if $no_size {
                        // because no_size packets are only on login server
                        omit_bytes = 1;
                    }
                )?
                $(
                    if $compressed_value {
                        // 4 bytes uncompressed + 2 bytes used by zlib
                        omit_bytes += 6;
                    }
                )?

                let mut internal_buffer: Vec<u8> = Vec::new();
                $(
                    if $compressed_value {
                        let data = &buffer[omit_bytes..];
                        let mut decoder = flate2::read::DeflateDecoder::new(data);
                        std::io::Read::read_to_end(&mut decoder, &mut internal_buffer).unwrap();
                    }
                )?

                let buffer = if internal_buffer.is_empty() {
                    buffer[omit_bytes..].to_vec()
                } else {
                    internal_buffer
                };

                let mut reader = std::io::Cursor::new(&buffer);

                Self {
                    $(
                        $field_name: BinaryConverter::read_from(
                            &mut reader
                        ).unwrap()
                    ),*
                }
            }

            // outcome
            pub fn to_binary(&mut self) -> Vec<u8> {
                let mut packet = Vec::new();
                $(
                    BinaryConverter::write_into(
                        &mut self.$field_name,
                        &mut packet
                    ).unwrap();
                )*
                let header = Self::_build_header(&packet);
                [header, packet].concat()
            }

            fn _build_header(body: &Vec<u8>) -> Vec<u8> {
                let mut header: Vec<u8> = Vec::new();
                $(
                    let size = body.len() + OUTCOMING_OPCODE_LENGTH;
                    byteorder::WriteBytesExt::write_u16::<byteorder::BigEndian>(
                        &mut header,
                        size as u16,
                    ).unwrap();
                    byteorder::WriteBytesExt::write_u32::<byteorder::LittleEndian>(
                        &mut header,
                        $opcode_value as u32
                    ).unwrap();
                )?

                header
            }

            $(
                pub fn unpack(&mut self) -> PacketOutcome {
                    ($opcode_value as u32, self.to_binary())
                }
            )?
        }
    };
}

This is sandbox to reproduce the issue.

Could somebody explain what I did wrong and how to fix the issue ?

You should interpret the error carefully. It says that an fn item can't capture dynamic environment. Therefore it suggests that you should replace the fn item with a closure.

Your opcode variable is not that function item. (Instead, It's the "environment" being captured.) It doesn't make any sense to replace it with a closure because that would change its type, and still not help with the fact that you would still be trying to capture it from ordinary fns (_build_header() and unpack()).

As it's currently standing, your code can't be made work as-is. If you want to use the opcode, you have to store it somewhere so that the functions can later refer to it. If you don't indeed want to replace the methods with closures (which would be a weird API), then you'll have to store it in a field of the generated structs and access it through self.

1 Like