Can I serialize a function pointer with using Serde?

The following codes can not compile because that function pointer can not be serialized:

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct MyStruct {
    f: fn(bool, bool) -> bool,
}

What can I do?

Not much. There's no practical/universal/meaningful way in which a function pointer can be serialized and then deserialized (on a potentially different machine).

Why are you trying to serialize a function pointer? What's your higher-level goal with this?

9 Likes

In this case, if you know the function has no side-effects, you can store a truth table instead:

#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
pub struct TruthTable2(u8);

impl<F:Fn(bool,bool)->bool> From<F> for TruthTable2 {
    fn from(f:F)->Self {
        TruthTable2( (f(true,true) as u8) << 3
                   | (f(true,false) as u8) << 2
                   | (f(false,true) as u8) << 1
                   | f(false,false) as u8
        )
    }
}

impl TruthTable2 {
    pub fn apply(self, a:bool, b:bool)->bool {
        1 & (self.0 >> ((a as u8) << 1 | (b as u8))) == 1
    }
}
5 Likes

For example, I have a type as follow:

struct LongTimeToGet {
    part1: Sig,
    part2: Sig,
    func: fn(bool, bool) -> bool,
}

I have to calculate long time to get an instance of LongTimeToGet, so I have to serialize it to local file that I can use it in the next running time. Or I can give the saved file to another person who is using the same crate as mine, so he can also use the instance directly.

Yeah, I get that you want to serialize the data, but it doesn't explain why you want to serialize a function. What purpose does func serve? Why does a seemingly pure data type contain a function pointer? That's very unusual, and that's the part I am asking about.

2 Likes

I thought it maybe a way to reduce the code amout.
The type LongTimeToGet implement trait MyTrait, and will be parsed to a function, where the type LongTimeToGet do something on its own. OutFn can not affect LongTimeToGet, it just call it:

impl MyTrait for LongTimeToGet {
    fn do_something() {}
}

fn OutFn(x: dyn MyTrait) {
    do things 
    x.do_something()
    do things
}

If I have four kinds of fn(bool, bool) -> like:

func_logic1(a: bool, b: bool) -> a && b
func_logic2(a: bool, b: bool) -> a || b
func_logic3(a: bool, b: bool) -> a && !b
func_logic4(a: bool, b: bool) -> !a && b

If I don't make function pointer as to be a LongTimeToGet's field, I have to define four different type of it:

struct LongTimeToGetLogic1 {
    part1: Sig,
    part2: Sig,
}
struct LongTimeToGetLogic2 {
    part1: Sig,
    part2: Sig,
}
struct LongTimeToGetLogic3 {
    part1: Sig,
    part2: Sig,
}
struct LongTimeToGetLogic4 {
    part1: Sig,
    part2: Sig,
}

impl MyTrait for LongTimeToGetLogic1 {
    fn do_something(&self) {
        self.part1 && self.part2
    }
}
impl MyTrait for LongTimeToGetLogic2 {
    fn do_something(&self) {
        self.part1 || self.part2
    }
}
impl MyTrait for LongTimeToGetLogic3 {
    fn do_something(&self) {
        self.part1 && !self.part2
    }
}
impl MyTrait for LongTimeToGetLogic4 {
    fn do_something(&self) {
        !self.part1 && self.part2
    }
}

But with function pointer, I can:

impl MyTrait for LongTimeToGet {
    fn do_something(&self) {
        self.f(self.part1, self.part2)
    }
}

To get a similar effect as the function pointer, you can use an enum. Roughly…

enum Logic {
    L1,
    L2,
    L3,
    L4,
}

struct LongTimeToGet {
    part1: Sig,
    part2: Sig,
    logic: Logic,
}

impl MyTrait for LongTimeToGet {
    fn do_something(&self) {
        match self.logic {
            Logic::L1 => self.part1 && self.part2,
            Logic::L2 => self.part1 || self.part2,
            Logic::L3 => self.part1 && !self.part2,
            Logic::L4 => !self.part1 && self.part2,
        }
    }
}

then the enum can derive Serialize.

8 Likes

In this case, it would probably be more principled to make the abstract behavior explicit using an enum or a generic parameter, and serialize this high-level description instead of trying to serialize raw machine code. Playground.

You can even go crazy and define an AST for arbitrary trees of Boolean operations, and evaluate it like an interpreter: Playground.

The point is, you have to create some abstract, platform-independent representation in order to stand a chance of serializing it.

2 Likes

I'm a bit late but you could use std::mem::transmute into a u64 or typecast the pointer if only generics are involved. I'm unsure how those would compare when running on different machines though.

Casting that back to a function pointer will be UB if you do it on a different machine, or even in a different run of the same binary on the same computer. Memory addresses are completely arbitrary at runtime¹, so you can't just send an address like this and expect it to be meaningful.


¹ With a few extremely rare exceptions, like MMIO registers on some embedded systems.

7 Likes

Strictly, binaries have base addresses that you can generally expect to be consistently used between different processes and machines, but it depends on too many things (kernel flags et al) to be trustworthy. Especially since serde is mostly for persistence or moving things between machines, where you can't even expect the same build to be used.

1 Like

Not really, due to ASLR.

2 Likes

Yes, your right now that generally nowadays even executables are relocated. I was somewhat loose with my language, dynamic libraries have always been relocatable of course (due to address conflicts), but the executable binary can only be relocated if you have PIE enabled at build.

I was thinking of Windows, where I remember it not being enabled by default (but perhaps that's changed?), but I forgot most Linux compiled code has pretty much always used position independent code anyway so they don't have to potentially run relocation patches on library load like Windows does (unless you bothered to carefully adjust all your library base addresses)

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.