Can't capture dynamic environment in a fn item when pass dynamic opcode into struct's method

I have a function where I need to pass dynamic opcode into struct, but I got an error:

error[E0434]: can't capture dynamic environment in a fn item
   --> src\client\movement\ai\movement.rs:188:26
    |
188 |                     vec![opcode]
    |                          ^^^^^^
    |
    = help: use the `|| { ... }` closure form instead

This is how I use the function:

#[derive(Packet, Debug)]
struct Outcome {
    guid: PackedGuid,
    movement_flags: u32,
    movement_flags2: u16,
    time: u32,
    x: f32,
    y: f32,
    z: f32,
    direction: f32,
    unknown: u32,
}

fn build_movement_packet(
        opcode: u32,
        guid: u64,
        new_position: Position,
        movement_flags: MovementFlags
    ) -> (u32, Vec<u8>) {
	let get_opcodes = move |opcode: u32| {
		impl Outcome {
			fn opcodes() -> Vec<u32> {
				vec![opcode]
			}
		}
	};

	get_opcodes(opcode.clone());
    // ...
}

I tried to follow the hint in error message (use closure), but seems like it cannot be done in this way. Could somebody explain if it possible to pass dynamic opcode into struct's method without saving it into fields ?

I'm not actually sure what you're trying to do here. You're creating a closure but the only thing the closure does is define an impl? Impls are global, they can't be defined dynamically.

I have a macro that add opcodes method to struct:

#[macro_export]
macro_rules! with_opcode {
    (
        $(@login_opcode($($login_opcode:expr),*))?
        $(@world_opcode($($world_opcode:expr),*))?

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

        $($PacketStructImpl: item)*
    ) => {
        $(#[$outer])*
        $vis struct $PacketStruct {
            $($(#[$field_attr])? $field_vis $field_name: $field_type),*
        }

         $($PacketStructImpl)*

        impl $PacketStruct {
            $(
                fn opcodes() -> Vec<u8> {
                    let opcodes: Vec<u8> = vec![$($login_opcode as u8),*];
                    opcodes
                }
            )?

            $(
                fn opcodes() -> Vec<u32> {
                    let opcodes: Vec<u32> = vec![$($world_opcode as u32),*];
                    opcodes
                }
            )?
        }
    };
}

I use opcodes method inside my proc macro to evaluate how much bytes to omit before std::io::Cursor can read:

use proc_macro::{TokenStream};
use proc_macro2::{Ident};
use syn::ItemStruct;
use syn::{parse_macro_input};
use quote::{quote, format_ident};

mod types;

use types::{Attributes, Imports};

#[proc_macro_derive(Packet, attributes(options, dynamic_field))]
pub fn derive_packet(input: TokenStream) -> TokenStream {
    let ItemStruct { ident, fields, attrs, .. } = parse_macro_input!(input);
    let Imports {
        cursor,
        binary_converter,
        deflate_decoder,
        read,
        types,
    } = Imports::get();

    let mut is_compressed = quote!(false);
    if attrs.iter().any(|attr| attr.path.is_ident("options")) {
        let attributes = attrs.iter().next().unwrap();
        let attr: Attributes = attributes.parse_args().unwrap();
        let value = attr.compressed.value;
        is_compressed = quote!(#value);
    }

    let field_names = fields.iter().map(|f| {
        f.ident.clone()
    }).collect::<Vec<Option<Ident>>>();

    let mut dynamic_fields: Vec<Option<Ident>> = vec![];
    for field in fields.iter() {
        let ident = field.ident.clone().unwrap().to_string();
        let field_type = field.ty.clone();
        let ident = format_ident!("{}", field.ident.as_ref().unwrap());

        if field.attrs.iter().any(|attr| attr.path.is_ident("dynamic_field")) {
            dynamic_fields.push(Some(ident));
        }
    }

    let initializers = field_names
        .iter()
        .map(|field_name| {
            if dynamic_fields.contains(field_name) {
                quote!{ Self::#field_name(&mut reader, &mut initial) }
            } else {
                quote!{ #binary_converter::read_from(&mut reader).unwrap() }
            }
        });

    let output = quote! {
        impl #ident {
            pub fn from_binary(buffer: &Vec<u8>) -> Self {
                let mut omit_bytes = Self::opcodes()[0].to_le_bytes().len();
                if omit_bytes == 4 {
                    // 2 bytes packet size + 4 bytes opcode size
                    omit_bytes += 2;
                }

                let mut buffer = match #is_compressed {
                    true => {
                        let mut internal_buffer: Vec<u8> = Vec::new();
                        // 4 bytes uncompressed + 2 bytes used by zlib
                        omit_bytes += 6;

                        let data = &buffer[omit_bytes..];
                        let mut decoder = #deflate_decoder::new(data);
                        #read::read_to_end(&mut decoder, &mut internal_buffer).unwrap();

                        internal_buffer.to_vec()
                    },
                    false => buffer.to_vec(),
                };

                let mut initial_reader = #cursor::new(&mut buffer);
                let mut initial = Self {
                    #(#field_names: #binary_converter::read_from(&mut initial_reader).unwrap()),*
                };

                let mut reader = #cursor::new(&mut buffer);
                Self {
                    #(#field_names: #initializers),*
                }
            }

            pub fn to_binary(&mut self) -> Vec<u8> {
                let mut body = Vec::new();
                #(
                    #binary_converter::write_into(
                        &mut self.#field_names,
                        &mut body
                    ).unwrap();
                )*

                body
            }

            pub fn unpack(&mut self) -> #types::PacketOutcome {
                let opcode = Self::opcodes()[0] as u32;
                (opcode, self.to_binary())
            }
        }
    };

    TokenStream::from(output)
}

also I use opcodes method for output.

Inside my project I have a place where I need to generate packets dynamically (smth like that, but this is primary branch where I have not merged macro feature yet).

So, I just trying to find the way how can I do the same in context of my macro.

P.S. usually I implement Income and Outcome structs, and since I can pass single opcode or multiple opcodes:

// like this
with_opcode! {
    @world_opcode(1)
}

// or this
with_opcode! {
    @world_opcode(1, 2, 3)
}

you can see I use only first. I did this for readability to see which opcodes are used with specific packet. I can do the same using comments but I cannot do code folding in comments. Multiple opcodes I use only for Income, just for clarify. This not functional for now.

P.P.S: with_opcode! usage is optional, but any struct where I derive my macro should have opcodes method. So I tried to manually create it using closure to solve the issue with dynamic opcode capturing.

If you need the opcode in Outcome's methods, you should make it a field on the struct.

1 Like