Live data from Hacker News

Native Reflection in Rust

jack.wrenn.fyi

11–20 of 68 posts

Re: Native Reflection in Rust

#11
post #5

Earlier quoted context omitted.

Reflection is usually not available in AoT compiled languages. The prevalent Rust coding styles rely heavily on monomorphic data types and functions, meaning there's nothing left to reflect at runtime. But if you want to deal with trait objects and need to access the underlying type, you need to use Any::downcast or rely on annotations on every type you want to reflect on. Or now, leverage DWARF info on Linux with de…

What are monomorphic data types? What should be my first read on the subject?

[deleted]

Re: Native Reflection in Rust

#12

Does using DWARF info imply that this will break when you strip the resulting executable? I often strip my Rust binaries because it practically halves the application size, which can become quite a lot in a language where you're statically linking everything. Regardless, quite an ingenious use of standard ELF features, I didn't think this would be possible in Rust without adding some kind of VM around reflection code…

Yes, unfortunately that's a tradeoff here. Rust does support splitting debug info into other files, but Deflect doesn't support loading split debuginfo yet.

Re: Native Reflection in Rust

#13
post #9

Earlier quoted context omitted.

What are monomorphic data types? What should be my first read on the subject?

It's a fancy way of saying "every time this type is used, replace all the generic type params with what was used and generate code for it". It's how generics are implemented in Rust. If you have struct Foo (T); And you create Foo(42i32) and Foo(0.0f64), the compiler will create the equivalent to struct Fooi32(i32); struct Foof64(f64); In other languages like Java, generics are implemented the way that Rust does "trai…

Nice examples - you can also have languages (like SML) where monomorphization is simply an implementation detail. Some compilers (e.g., MLton) perform monomorphization and others don't.

Re: Native Reflection in Rust

#14
post #8

Earlier quoted context omitted.

Reflection is typically provided by a runtime, and languages that don't have runtimes usually don't have it. You shouldn't expect a low-level systems language to have reflection. There is no zero-cost way of implementing it.

Except Rust has runtime: [0]. And so, usually, does C (in hosted implementations). [0] https://doc.rust-lang.org/reference/runtime.html

These are a couple of functions executables can call at run time, but they're more like an extra standard library. It's not a runtime in the same sense as a runtime in dynamic or GC languages that manages all objects and is able to know types of arbitrary objects and inspect/trace them.

Rust has no run-time type information except limited downcasts via `dyn Any` or explicitly derived traits on per-type basis, and these features compile to type-specific monomorphic code rather than calling some run-time reflection.

Re: Native Reflection in Rust

#15
post #9

Earlier quoted context omitted.

What are monomorphic data types? What should be my first read on the subject?

It's a fancy way of saying "every time this type is used, replace all the generic type params with what was used and generate code for it". It's how generics are implemented in Rust. If you have struct Foo (T); And you create Foo(42i32) and Foo(0.0f64), the compiler will create the equivalent to struct Fooi32(i32); struct Foof64(f64); In other languages like Java, generics are implemented the way that Rust does "trai…

I think cpp does this too

Re: Native Reflection in Rust

#16
post #9

Earlier quoted context omitted.

What are monomorphic data types? What should be my first read on the subject?

It's a fancy way of saying "every time this type is used, replace all the generic type params with what was used and generate code for it". It's how generics are implemented in Rust. If you have struct Foo (T); And you create Foo(42i32) and Foo(0.0f64), the compiler will create the equivalent to struct Fooi32(i32); struct Foof64(f64); In other languages like Java, generics are implemented the way that Rust does "trai…

To add to this, even the Foo-wrapper is gone, just the i32 remains. Rust values are amorphous data blobs at runtime.

Re: Native Reflection in Rust

#17
post #9

Earlier quoted context omitted.

What are monomorphic data types? What should be my first read on the subject?

It's a fancy way of saying "every time this type is used, replace all the generic type params with what was used and generate code for it". It's how generics are implemented in Rust. If you have struct Foo (T); And you create Foo(42i32) and Foo(0.0f64), the compiler will create the equivalent to struct Fooi32(i32); struct Foof64(f64); In other languages like Java, generics are implemented the way that Rust does "trai…

Expanding on trait objects: these are implemented as "V-Tables", structs holding pointers to the trait's methods and to the underlying type. This means that if you need to know what the underlying type, you have to do something fancy, usually referred to as "reflection". Also, invocation of generic functions that use V-Tables require "chasing pointers", which makes cache locality worse (because data might not be in the same cache read as the v-table itself), but makes the generated binary smaller (because if you have something like Foo used with 1000 types, with monomorphization you end up with 2000 generated types in the binary, instead of 1001 with trait objects).

Re: Native Reflection in Rust

#18
post #9

Earlier quoted context omitted.

What are monomorphic data types? What should be my first read on the subject?

It's a fancy way of saying "every time this type is used, replace all the generic type params with what was used and generate code for it". It's how generics are implemented in Rust. If you have struct Foo (T); And you create Foo(42i32) and Foo(0.0f64), the compiler will create the equivalent to struct Fooi32(i32); struct Foof64(f64); In other languages like Java, generics are implemented the way that Rust does "trai…

Pretty sure that some usage patterns of polymorphic types can not be completely monomorphized. Here's example in Golang:

    package main

    import (
        "fmt"
    )

    type wrapper[T any] struct {
        Value T
    }

    func (w wrapper[T]) String() string {
        return fmt.Sprintf("{%v}", w.Value)
    }

    func stringWrapped[T any](n int, v T) string {
        if n == 0 {
            return fmt.Sprintf("%v", v)
        }
        return stringWrapped(n-1, wrapper[T]{Value: v})
    }

    func main() {
        n := 0
        fmt.Scanf("%d", &n)
        result := stringWrapped(n, "test")
        fmt.Println(result)
    }
Go refuses to compile because it can't possibly generate all instances of wrapper[T] that this program may use: wrapper[string], wrapper[wrapper[string]], wrapper[wrapper[wrapper[string]]], etc.

Re: Native Reflection in Rust

#19
post #16
post #9

Earlier quoted context omitted.

It's a fancy way of saying "every time this type is used, replace all the generic type params with what was used and generate code for it". It's how generics are implemented in Rust. If you have struct Foo (T); And you create Foo(42i32) and Foo(0.0f64), the compiler will create the equivalent to struct Fooi32(i32); struct Foof64(f64); In other languages like Java, generics are implemented the way that Rust does "trai…

To add to this, even the Foo-wrapper is gone, just the i32 remains. Rust values are amorphous data blobs at runtime.

Yes, that's true but that is an implementation detail that only comes into play when dealing with ABI, and then you should be using #[repr(transparent)] to ensure that the compiler won't do something else :)

Re: Native Reflection in Rust

#20
post #8

Earlier quoted context omitted.

Reflection is typically provided by a runtime, and languages that don't have runtimes usually don't have it. You shouldn't expect a low-level systems language to have reflection. There is no zero-cost way of implementing it.

Except Rust has runtime: [0]. And so, usually, does C (in hosted implementations). [0] https://doc.rust-lang.org/reference/runtime.html

[deleted]
Post reply on HN