function doesn’t have a size known at compile-time
By: George Shuklin
Tags: Rust, Learning To Code, Programming, Data Structures, Type Theory

function doesn’t have a size known at compile-time

From TupleStruct to struct with function

I tried to refactor this code:

let picture = (|x:Float, y:Float| sin(x)*x/2.0 + x — y, X as f32, Y as f32);

Into use of a structure.

My code at Nth iteration which still does not work:

type RootFunc = Fn(f32, f32) ->f32 ; struct Picture{

func: RootFunc,

float_x: f32,

float_y: f32

}

The error I get is reasonable, but my brains aren’t large enough to process the whole problem.

--> src/main.rs:29:5

|

29 | func:RootFunc,

| ^^^^^^^^^^^^^ doesn't have a size known at compile-time

Lyrical part

My ‘rust learning’ process consists of three modes: awe (when I found that Rust really fast and help me to avoid errors); lore frustration (when I can’t make a simple thing work, mostly with graphical stack or libraries), and a total helplessness when I stumble upon Serious Stuff in Rust.

Lore frustration is normal, I got used to it at my job. It’s the way you learn something in a hard way.

But that Serious Stuff is a new thing to me. Older languages I’m familiar with are so easy to explain, that most of the time any language-level problem is folding into few minutes of thinking and reading. After that you are either understood you mistake, or understood why this is necessary, or, in rare cases, you’re disappointed in the language design.

But Rust pushes me to some new unholy realm, when I have to crack and squeeze my brains beyond point of plasticity (causing irreversible changes).

The whole mental model of Rust is so far away from usual finger-pointing triviality, that it just does not fit into my brains operational space. Is it a true learning?

Anyway, let’s go back to the problem.

Reasoning on error

When I had an anonymous TupleStruct with a function in it, it was simple: there is a function and two floats. Combine them together and you got your TupleStruct. The concrete type with the backed-in function, is automatically calculated.

When I tried to convert it to a proper structure with associated functions I asked Rust to build something which can work with ‘any function’ which takes two floats and returns one.

What’s the problem? The problem is that Rust can’t guess the size of ‘arbitrary function’ at compile time.

Why? My current idea that the size of the function is the size of the ‘function body’. One-liner has different size then hundred-liner.

Let’s check this.

use std::mem::size_of_val;



fn main() {

let x = & (|x: f32, y: f32|{ x + y}, 0 as f32, 0 as f32);

let y = & (|x: f32, y: f32|{ x + y + 9.0}, 0 as f32, 0 as f32);

println!("{}", size_of_val(x));

println!("{}", size_of_val(y));

}

Both yield 8. This contradicts my theory (that the tuple includes the function body). The fist tuple lives happily with size of two f32 (32+32), and the second one just ignores an additional constant (9), so body is not counted.

So, why can’t I have a struct with an arbitrary function in it? In my (N-1)th attempts I was able to make it compile with with this:

type RootFunc = Fn(f32, f32) ->f32; struct Picture{

func:&'static RootFunc,

float_x: f32,

float_y: f32

}

It compiles but It got slapped with this:

warning: trait objects without an explicit `dyn` are deprecated

--> src/main.rs:26:17

|

26 | type RootFunc = Fn(f32, f32) ->f32 ;

| ^^^^^^^^^^^^^^^^^^ help: use `dyn`: `dyn Fn(f32, f32) ->f32`

I don’t want to use dyn traits here, as it’s an indirect call by a pointer (actually, that’s what I wrote — store a pointer to a function).

So, I don’t want to have a pointer, I want a compile time optimization — every user of that struct going to be monomorphized.

Yes, and how Rust can do it? Only with generics, right? If you want monomorphization, you use generics. That was a good reasoning, I hope.

Generic structure

The problem here is that I want to a function to be generic. That means a structure with a type parameter.

struct Picture<F>{

func: F,

float_x: f32,

float_y: f32

}

I don’t like that it’s too generic here, as I provided not bounds on the ‘F’.

Can I do this? (I will move to implementing it as soon as I write a satisfying struct).

I clearly forgot about ‘bounds in type parameter for type’, but after quick refreshing I was able to write this:

struct Picture<F: Fn(f32,f32)-> f32>{

func:F,

float_x: f32,

float_y: f32

}

Compiler accepted it with no complains.

Implementing for the structure

impl Picture<F: Fn(f32,f32)-> f32>{

fn new<F>(func:F, float_x: f32, float_y: f32){

Picture{func:F, float_x:float_x, float_y:float_y}

}

}

And hell opens loose.



--> src/main.rs:42:14

|

42 | impl Picture<F: Fn(f32,f32)-> f32>{

| ^^^^^^^^^^^^^^^^^^^^

|

= note: for more information, see error[E0658]: associated type bounds are unstable--> src/main.rs:42:1442 | impl Picture f32>{| ^^^^^^^^^^^^^^^^^^^^= note: for more information, see https://github.com/rust-lang/rust/issues/52662 error[E0107]: wrong number of type arguments: expected 1, found 0

--> src/main.rs:42:6

|

42 | impl Picture<F: Fn(f32,f32)-> f32>{

| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected 1 type argument error[E0229]: associated type bindings are not allowed here

--> src/main.rs:42:14

|

42 | impl Picture<F: Fn(f32,f32)-> f32>{

| ^^^^^^^^^^^^^^^^^^^^ associated type not allowed here

I done Terrible somthing here. I invoked higher demons I have no permission to pest, shall me die.

It’s not what I’ve meant.

I wanted to say that this is an implementation for a generic structure. OK, may be higher order type parameters (I suspect this is the name for the demon I called incidentally) is a wrong idea.

Let’s try to avoid type parameter for impl.

impl Picture{

fn new<F>(func:F, float_x: f32, float_y: f32)

where F: Fn(f32, f32)-> f32{

Picture{func:func, float_x:float_x, float_y:float_y}

}

}

But then there is a simple error:

42 | impl Picture{

| ^^^^^^^ expected 1 type argument

So I must to pass a type into impl , mustn’t I?

How?

I run though a chapter in Rust Book. They have this:

struct Point<T> {

x: T,

y: T,

}



impl<T> Point<T> {

fn x(&self) -> &T {

&self.x

}

}

Let’s do this…

impl<F> Picture<F>{

fn new(func:F, float_x: f32, float_y: f32) -> Self

{

Picture{func:func, float_x:float_x, float_y:float_y}

}

}

(yes, I forgot about return value last time)

Now Rust is becoming helpful:

28 | struct Picture<F: Fn(f32,f32)-> f32>{

| ------------------------------------ required by `Picture`

...

42 | impl<F> Picture<F>{

| - ^^^^^^^^^^ expected an `Fn<(f32, f32)>` closure, found `F`

| |

| help: consider restricting this bound: `F: std::ops::Fn<(f32, f32)>`

|

Let’s me do as it says:

impl<F: Fn(f32,f32) -> f32> Picture<F>{

fn new(func:F, float_x: f32, float_y: f32) -> Self

{

Picture{func:func, float_x:float_x, float_y:float_y}

}

}

Finally, it compiled. Moreover, I understand most of the errors I got.

It was hurd. (Hard and hurt).

Post-thinking

The problem I run around in 1..(N-1) attempts was that I needed to declare <F> in two places: in impl<F> and in Picture<F> .

Why? I’ll try to lay down my logic, but there’s 99% chance I’m wrong.

The first F ( impl<F> ) is a type parameter declaration for impl . We declare that impl is a generic, e.g. it can work for multiple different F (functions in our case), so every new use of a new function from this impl will produce a monomorphized code.

The second use of ‘F’ ( Picture<F> ) is a USE of that type. We are saying here that we implement code (all functions in a body) for a structure with a given type parameter F. Basically, we are passing ‘F’ as a parameter to the structure.

… Let me check this.

struct Picture<F: Fn(f32,f32)-> f32, G>{

func:F,

func2: G,

float_x: f32,

float_y: f32

}

impl<F: Fn(f32,f32) -> f32,> Picture<F,F>{

fn new(func:F, func2:F, float_x: f32, float_y: f32) -> Self

{

Picture{func:func, func2:func2, float_x:float_x, float_y:float_y}

}

}

It’s really hard to spell this…

We declare a generic structure with two type parameters: F (which is function bounded, and G — free parameter). We declare the implementation for this structure when F is function ( f32, f32->f32 ) and G is the function with the same type.

Basically, we instantiate Picture — it provided no bounds for G , but the implementation we provided supports only situations when func2 has the same type as F . Because we know everything we need for F , this code can compile, as G is the same as F . If G is different, then there is no implementation for this situation. I hope I got it right.

Last piece of headache I’m forcing on me. Can I have two impl with different set of bounds? I think I can’t. I’ve tried but Rust scold me:

duplicate definitions for `new`

(Even when I had different impl sections). I really at border of my brain capabilities here, so I just accept that I can’t, whiteout trying to generalize why.

Last piece for today

I gonna refactor the code to use a generic structure instead of TupleStructure…

I’ve done it. Few typos, but I know what I’m doing. It was the hardest commit for today. I but hope I learned some important Rust aspects. Learning Rust is HARD. Using is a pleasure, but learning … oh, it’s hard!

(Btw, I like this post because I was able somewhat to capture the huge thinning behind some trivially looking changes in git).