Search code examples
performancerustsystems-programming

Is there any overhead in Rust-style method calling approach compared to the usual use of functions in other system programming languages such as C?


I am just starting to use Rust, under the impression of its ownership paradigm. Reading the tutorial, I found the following code

let secret_number = rand::thread_rng().gen_range(1, 101);

I assume that a random integer is generated after instantiating of the rand::ThreadRng structure by calling its method.

Isn't it better for performance simply to use functions like in C? Since Rust positions itself as a system programming language and if I am right in my conjecture, this choice in favor of creating structures and using their methods seems to be quite suboptimal.


Solution

  • Is there any overhead in Rust-style method calling approach compared to [...] C?

    No.

    The exact same machine instructions can be generated by both two languages. The syntax to express the concepts does not require that the resulting code be inefficient (and in many cases the opposite is true).

    Isn't it better for performance simply to use functions like in C?

    Methods are functions.

    A Rust method is conceptually the same as a C function taking a pointer:

    Rust

    struct Foo {
        a: i32,
    }
    
    impl Foo {
        fn add(&self, b: i32) -> i32 {
            self.a + b
        }
    }
    

    C

    struct Foo {
        int a;
    };
    
    int Foo_add(struct Foo *self, int b) {
        return self->a + b;
    }
    

    There's no important difference between the two languages here.

    Rust has free functions as well as associated functions; if you don't need to take a reference to data, you don't need to:

    Rust

    struct Foo {
        a: i32,
    }
    
    // Free function
    fn foo() -> Foo { 
        Foo { a: 42 }
    }
    
    impl Foo {
        // Associated function
        fn new() -> Foo {
            Foo { a: 99 }
        }
    }
    

    C

    struct Foo {
        int a;
    };
    
    struct Foo foo() {
        struct Foo foo = { 42 };
        return foo;
    }
    
    struct Foo Foo_new() {
        struct Foo foo = { 99 };
        return foo;
    }
    

    Rust also has zero sized types which look like they have associated data but which disappears in the compiled code:

    Rust

    // Zero size
    struct Foo;
    
    impl Foo {
        fn add_one(&self, b: i32) -> i32 {
            b + 1
        }
    }
    

    C

    int Foo_add_one(int b) {
        return b + 1;
    }
    

    creating structures and using their methods seems to be quite suboptimal

    Creating structures and having related methods is common in C code. I'd say that it's most likely the predominant coding style; I don't know why you'd say it's suboptimal.


    Your example case isn't useful to compare because you haven't provided any C code, much less something equivalent. ThreadRng does a lot of work to provide a better experience for generating random numbers:

    ThreadRng uses ReseedingRng wrapping the same PRNG as StdRng, which is reseeded after generating 32 MiB of random data. A single instance is cached per thread and the returned ThreadRng is a reference to this instance — hence ThreadRng is neither Send nor Sync but is safe to use within a single thread. This RNG is seeded and reseeded via EntropyRng as required.

    The "traditional" random number generator (rand) has a global state (which is generally a bad thing), but conceptually looks like:

    struct RngState {
        int whatever_goes_here;
    };
    
    static struct RngState GLOBAL_STATE = { 0 };
    
    int rand_with_state(struct RngState *state) {
        return state->whatever_goes_here++;
    }
    
    int rand() {
        return rand_with_state(&GLOBAL_STATE);
    }
    

    Note that it still uses a pointer to the data.