const_format_crates
const_format_crates copied to clipboard
Generic support is possible?
Feature request
I tried to use formatcp with generics, which caused an error. Is it possible to formatcp support generics?
use const_format::formatcp;
pub trait Named {
const NAME: &'static str;
}
struct Foo;
impl Named for Foo {
const NAME: &'static str = "Foo";
}
struct Box<T>(T);
impl<T: Named> Named for Box<T> {
const NAME: &'static str = formatcp!("{}Box", <T as Named>::NAME);
}
#[test]
fn test() {
assert_eq!(Foo::NAME, "Foo");
assert_eq!(Box::<Foo>::NAME, "FooBox");
}
errors
error[E0401]: can't use generic parameters from outer function
|
| impl<T: Named> Named for Box<T> {
| - type parameter from outer function
| const NAME: &'static str = formatcp!("{}Box", <T as Named>::NAME);
| ^ use of generic parameter from outer function
error[E0080]: evaluation of constant value failed
|
| const NAME: &'static str = formatcp!("{}Box", <T as Named>::NAME);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ referenced constant has errors
|
= note: this error originates in the macro `$crate::pmr::__formatcp_impl` which comes from the expansion of the macro `formatcp` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0080]: evaluation of constant value failed
|
| const NAME: &'static str = formatcp!("{}Box", <T as Named>::NAME);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ referenced constant has errors
|
= note: this error originates in the macro `__cf_osRcTFl4A::__concatcp_inner` which comes from the expansion of the macro `formatcp` (in Nightly builds, run with -Z macro-backtrace for more info)
This would be really useful. If this worked, it could reduce heap allocations in my crate to almost zero, as opposed to multiple String allocations on every query.
Any updates? It is really useful
I think this is a limitation of the rust language, not of this library. There is some initiative which is planning to support custom types as const parameters but I don't remember the name.
Is there an alternative solution, I'm having the same problem😢
It could be done like this:
pub trait Named {
const NAME: &'static str;
}
struct Foo;
impl Named for Foo {
const NAME: &'static str = "Foo";
}
struct Box<T>(T);
impl<T: Named> Named for Box<T> {
const NAME: &'static str = buf_and_len_to_str(&concat_buf(<T as Named>::NAME, "Box"));
}
const fn buf_and_len_to_str(buf_len: &'static ([u8; 60], usize)) -> &'static str {
let buf = &buf_len.0;
let len = buf_len.1;
assert!(len < buf.len(), "buf is too long");
// I didn't find a way to slice an array in const fn
let buf = unsafe { core::slice::from_raw_parts(buf.as_ptr(), len) };
match core::str::from_utf8(buf) {
Ok(s) => s,
Err(_) => panic!(),
}
}
const fn concat_buf(left: &'static str, right: &'static str) -> ([u8; 60], usize) {
let mut buf = [0u8; 60];
let mut i = 0;
while i < left.len() {
buf[i] = left.as_bytes()[i];
i += 1;
}
while i - left.len() < right.len() {
buf[i] = right.as_bytes()[i - left.len()];
i += 1;
}
(buf, i)
}
#[test]
fn test() {
assert_eq!(Foo::NAME, "Foo");
assert_eq!(Box::<Foo>::NAME, "FooBox");
}
One problem (that can be mitigated) is that the whole buf would be in the final binary:
7th are got from changing let mut buf = [0u8; 60]; to let mut buf = [b'7'; 60];
Wow, thats really ~~hacky~~ awesome! When you said it can be mitigated, you didn't by any chance mean the whole buffer ending up in the binary? Probably not... still many thanks :D
you didn't by any chance mean the whole buffer ending up in the binary? Probably not... still many thanks :D
You can compute the length of the buffer and pass it by generics, so no space will be wasted. But even without that, overhead is very low, std stuff, panic debug prints, debuginfo etc are using a lot more space
Sorry for not responding this entire time.
@Ddystopia You're right that you can do it with a fixed size buffer, but the issue with this approach is that it can do any of these:
- leave the concatenated string truncated
- panic because the returned buffer isn't large enough to fit the concatenated strings
- (conditional on truncation) panic because the returned buffer doesn't end in valid utf8
(I believe that the const_generic_exprs unstable feature is necessary to do this properly, so that the resulting string is exactly as long as needed, but I haven't used this feature at all so I don't know its limitations)
Also, you can subslice a slice in const (since Rust 1.71.0) with split_at.
Sorry for not responding this entire time.
@Ddystopia You're right that you can do it with a fixed size buffer, but the issue with this approach is that it can do any of these:
* leave the concatenated string truncated * panic because the returned buffer isn't large enough to fit the concatenated strings * (conditional on truncation) panic because the returned buffer doesn't end in valid utf8(I believe that the
const_generic_exprsunstable feature is necessary to do this properly, so that the resulting string is exactly as long as needed, but I haven't used this feature at all so I don't know its limitations)Also, you can subslice a slice in const (since Rust 1.71.0) with
split_at.
I want to make it clear that I left this here because someone wanted a workaround. Of course, it's too limited to be in this library. It's just that someone said that this feature would help them get rid of allocations, and for the sake of that, this kind of thing is as good as it gets. Thank you for your response
Also, that code will always panic when buffer is not large enough and will not leave concatenated string truncated. There will also be no utf-8 issues. The reason is that copy loops are not dependent over the buffer length.
About the incomplete generic_const_exprs feature. It can be done, but it is even more unergonomic than the previous workaround.
#![feature(generic_const_exprs)]
pub trait Named {
const NAME: &'static str;
}
struct Foo;
impl Named for Foo {
const NAME: &'static str = "Foo";
}
struct Box<T>(T);
impl<T: Named> Named for Box<T>
where
[(); <T as Named>::NAME.len() + "Box".len()]:,
{
const NAME: &'static str = {
buf_and_len_to_str::<{ <T as Named>::NAME.len() + "Box".len() }>(&concat_buf::<
{ <T as Named>::NAME.len() + "Box".len() },
>(<T as Named>::NAME, "Box"))
};
}
const fn buf_and_len_to_str<const LEN: usize>(buf_len: &([u8; LEN], usize)) -> &str {
let buf = &buf_len.0;
let len = buf_len.1;
let buf = buf.split_at(len).0;
match core::str::from_utf8(buf) {
Ok(s) => s,
Err(_) => panic!(),
}
}
const fn concat_buf<const LEN: usize>(left: &str, right: &str) -> ([u8; LEN], usize) {
let mut buf = [b'7'; LEN];
let mut i = 0;
while i < left.len() {
buf[i] = left.as_bytes()[i];
i += 1;
}
while i - left.len() < right.len() {
buf[i] = right.as_bytes()[i - left.len()];
i += 1;
}
(buf, i)
}
or
pub trait Len {
const LEN: usize;
}
impl Len for Foo {
const LEN: usize = <Self as Named>::NAME.len();
}
impl<T: Len> Len for Box<T> {
const LEN: usize = <T as Len>::LEN + "Box".len();
}
impl<T: Named + Len> Named for Box<T>
where
[(); <Self as Len>::LEN]:,
{
const NAME: &'static str = {
buf_and_len_to_str::<{ Self::LEN }>(&concat_buf::<{ Self::LEN }>(<T as Named>::NAME, "Box"))
};
}