-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new: typetraits.getTypeId #13305
new: typetraits.getTypeId #13305
Conversation
There is already signatureHash and symBodyHash in macros module to solve the same problem. |
could you please elaborate, and show how signatureHash (or symBodyHash) can be used in place of getTypeid? import std/macros
macro getTypeid2(T: typed): untyped =
newLit signatureHash(T)
template getTypeid3(T: typed): untyped =
type T2 = T
getTypeid2(T2)
type MyInt = int
echo getTypeid2(MyInt)
echo getTypeid2(int)
echo getTypeid2(int)
# echo getTypeid2(type(1)) # Error: node is not a symbol
echo getTypeid3(type(1))
echo getTypeid3(type(1))
echo getTypeid3(int)
echo getTypeid3(int) prints:
furthermore, even if it did work with signatureHash, it's a lot more efficient as it simply returns what the compile already knows (the |
The type IDs are not stable though and will end up in some .compileTime cache if we expose them and this can produce terrible bugs once IC arrives. Whatever we expose must be as stable as possible. Git also exposes commit hashes, not simple int32 values. For good reasons. |
I know, and I can improve the docs to make that clear, and maybe rename to
Regardless, this feature solves real problems that are hard/impossible to solve without it. Here are just 2 examples: passing a callback to a proc that handles multiple types:this defines a generic ## prettys.nim:
type Callback* = proc(result: var string, a: pointer, id: int): bool
import std/typetraits
proc pretty[T](result: var string, a: T, callback: Callback) =
when T is object:
result.add "("
for k,v in fieldPairs(a):
result.add $k & ": "
pretty(result, v, callback)
result.add ", "
result.add ")"
elif T is ref|ptr:
if callback(result, cast[pointer](a), getTypeid(T)):
discard
elif a == nil:
result.add "nil"
else:
pretty(result, a[], callback)
else:
result.add $a
proc pretty*[T](a: T, callback: Callback): string = pretty(result, a, callback)
## main.nim:
import prettys, std/typeid
proc main()=
type Foo = ref object
x: int
type Bar = object
b1: Foo
b2: string
let f = Bar(b1: Foo(x: 12), b2: "abc")
proc callback(ret: var string, a: pointer, id: int): bool =
case id
of Foo.getTypeid:
ret.add $("custom:", cast[Foo](a).x)
return true
else:
discard
echo pretty(f, callback)
proc callback2(ret: var string, a: pointer, id: int): bool =
case id
of Foo.getTypeid:
ret.add $("custom2:", cast[Foo](a).x)
return true
else:
discard
echo pretty(f, callback2)
main() prints:
I'm using exportc callback that handles multiple types# module a:
import ./types
use callbackFun(...) # module types:
define types ...
proc callbackFun(a: pointer, id: int) {.importc.}
import std/typetraits
proc callbackFun*[T](a: T) = callbackFun(cast[pointer](a), getTypeid(T)) implementation of callbackFun: say, this carries a lot of dependencies that we don't want to add to all modules that use # module impl:
proc callbackFun(a: pointer, id: int) {.exportc.} =
case id
of getTypeid(Foo1): echo cast[Foo1](a)
of getTypeid(Foo2): echo cast[Foo2](a)
else: doAssert false, $id I'm using this to customize the compiler using callbacks and it requires with very little changes to compiler sources, unlike other solutions which would require a lot of refactorings for this to work. |
Wouldn't a more natural implementation use typedescs and compare them? |
I don't see how this could work, see the 2 examples I gave in #13305 (comment) (nor does it help with type hashing); you'd be forced to use a generic in order to use typedescs, which can't work in those use cases. If you disagree, please post an answer with more details (or working code, ideally) |
Your use cases do not depend on the fact that a type ID is an int. I'm arguing it should be a |
What about making type descriptions values at runtime, as well as compile time? Transparently represent as a pointer (or string, or int or whatever)? After all, that's what getTypeID is doing anyway - retrieving a representation of a typedesc that can be used at runtime. |
I don't understand; if it's available at compile time it's available at runtime too, both these works: const c1 = getTypeid(type(12))
let c2 = getTypeid(type(12))
doAssert c1 == c2 Unless you'd talking about dynamic class id in the context of object inheritance, in which case that's out of scope for this PR.
I've now changed it to string instead of int (simply by stringifying id); the id itself can be changed in future implementations, and is (obviously) not assumed stable across compiler releases |
What's wrong with using the type's hash value though? |
std::type_index or std::type_info are better for guaranteeing uniqueness; if we're also considering shared libraries maybe the picture is a bit different, but here i'm concerned about use cases given above and
type Foo = object
const id: string = typeHash(Foo) the advantage of TType* {.acyclic.} = object of TIdObj # \
# types are identical iff they have the
# same id; there may be multiple copies of a type
# in memory! note that's it's still "in theory", because that comment is a lie right now; which this PR over-comes to some extent (at least for all primitive types; not yet for things like tuple or seq); nothing that can't be fixed though IMO |
@timotheecour As far as I know, you currently cannot create a Table[typedesc, string] at runtime - you must either use getTypeId (this proposal) or get the name of the type. What I'm trying to suggest is allowing the above by essentially calling getTypeId behind the scenes (or representing a typedesc as some other opaque value). |
@Varriount that's because an instance of a typedesc is not a value (eg something with a fixed import std/typetraits
type Foo = object
x1: string
type t = (int, Foo)
var names = ["myint", "myfoo"]
echo ($t.get(0), names[0]) anyhow what you're describing is out of scope for this PR |
Hash collisions are very rare and we can always change the implementation to |
aa18e1f
to
7ce3925
Compare
PTAL now that the pending PR got merged.
it now is a string; implementation simply uses $id, but is un-specified and allowed to change in future PRs
that doesn't seem robust, depending on what |
Well it has to be |
Would it be better to make the string distinct? |
done, PTAL |
81b1027
to
3591954
Compare
ping @Araq |
Yeah well, but the integer ID (even if exposed as a distinct string) is fragile, who will read its documentation and ensure they are not serialized? This might be one foundation for an entity-component-system. One where every recompile is a gamble -- a recipe for instabilities. |
Can't we instead make |
Yeah, that's one thing that would make me much more comfortable with this PR - disclaimers stating what API/ABI promises are being made with regards to the value being returned. |
This pull request has been automatically marked as stale because it has not had recent activity. If you think it is still a valid PR, please rebase it on the latest devel; otherwise it will be closed. Thank you for your contributions. |
baaee47
to
b2af1f1
Compare
b2af1f1
to
d3b0665
Compare
ping @Araq , I've fixed bitrot again. This PR is useful, solves real problems that are hard/impossible to do without this feature, as mention in description as well as illustrated in the tests and I'd like this feature to get merged. I've made the return type a distinct string as was requested. The doc comment in This feature is present in lots of languages, for good reasons; eg:
and even these:
In any case, if someone needs |
This makes pooling continuation allocators easier. |
@Araq this cannot work.
when true:
import std/typetraits
proc fn[T](a: T): auto =
static: echo "compiling fn" # this will print twice, both A's are distinct types
block:
type A = object
fn(A.default)
echo A.getTypeId
block:
type A = object
fn(A.default)
echo A.getTypeId with this PR, I get 2 distinct ids (754974726, 754974746), which is correct. With a signature based hash, you'd either get a collision or you'd have to look at the symbol id (defeating whole purpose of ignoring the typeid, as you'd still loose any guarantees wrt stability against unrelated changes after re-compilation). just for kicks I tried to see what import std/typetraits
type Foo[T] = object
echo Foo[int].getTypeIdViaHashType # __SpGTocwyb9chaUYWzXQMGSg
echo Foo[float].getTypeIdViaHashType # __SpGTocwyb9chaUYWzXQMGSg |
This is useful for generating type IDs for an ECS framework for game engines, especially ones with hot code reloading. The best C++ solution I've seen seems quite hacky: https://skypjack.github.io/2020-03-14-ecs-baf-part-8/ (uses |
This pull request has been automatically marked as stale because it has not had recent activity. If you think it is still a valid PR, please rebase it on the latest devel; otherwise it will be closed. Thank you for your contributions. |
new: typetraits.getTypeId: get a unique id (int) for a type (note: static, not dynamic), can be used for type equality, reliable type hashing (more reliable than via
$T
), + many other use cases that would be really hard without thisthis is much simpler and more efficient than something like getTypeId from https://github.com/yglukhov/variant/blob/master/variant.nim which relies on stringification