objs
objs is a small, zero-dependency Rust macro library for creating named and unnamed (anonymous) objects on the fly — without hand-writing a struct declaration every time you need a quick, ad-hoc bundle of fields.
It's useful for test fixtures, one-off "record" values, lightweight configuration objects, and anywhere you'd reach for an anonymous struct or object literal in other languages, but want to stay in idiomatic, zero-cost Rust.
use obj;
let point = obj! ;
assert_eq!;
assert_eq!;
Under the hood, every macro in this crate expands to a completely ordinary Rust struct. There's no runtime overhead, no dynamic typing, and no hidden allocations — you get a real, statically-typed value that the compiler treats exactly like any struct you'd write by hand.
Table of contents
- Installation
- Overview
obj!— anonymous, explicitly-typed objectsinfer_obj!— anonymous, type-inferred objectslet_obj!— named objects bound to a variable- Choosing the right macro
- Attributes and derives
- Implementing traits and methods
- Limitations
- License
Installation
Add objs to your Cargo.toml:
[]
= "0.1"
All three macros are exported from the crate root, so you can bring in exactly what you need:
use ;
Overview
| Macro | Produces | Type name | Types | Supports Attributes | Supports impl |
Reusable type? |
|---|---|---|---|---|---|---|
obj! |
Anonymous object | Object (block-scoped) |
Explicit, per-field | ✅ (before fields) | ✅ | ❌ (scoped to the macro's block) |
infer_obj! |
Anonymous object | Object (block-scoped, generic) |
Inferred from values | ✅ (before fields) | ❌ | ❌ (scoped to the macro's block) |
let_obj! |
Named object + variable | Whatever name you choose | Explicit, per-field | ✅ (before object name) | ✅ | ✅ |
obj! — anonymous, explicitly-typed objects
obj! builds a one-off object with explicitly annotated field types. It expands to a locally-scoped struct Object { .. } plus an initialized instance of it, all wrapped in a block expression.
Syntax
obj! {
[#[attributes]]
field_name: FieldType = value,
...
[; impl [Trait] { .. } ...]
}
- Fields are comma-separated
name: Type = valuepairs. A trailing comma is optional, and the field list may be empty. - Optional attributes (like
#[derive(Debug)]) may precede the field list and are forwarded onto the generated struct. - After a
;, you may add one or moreimpl [Trait] { .. }blocks to give the object methods or trait implementations.
Examples
Basic usage:
use obj;
let point = obj! ;
assert_eq!;
assert_eq!;
Implementing a trait:
use obj;
use fmt;
let person = obj! ;
assert_eq!;
Adding inherent methods with a bare impl { .. } block (no trait):
use obj;
let calculator = obj! ;
assert_eq!;
An empty object is valid too:
use obj;
let empty = obj! ;
drop;
Note: the generated struct is always named
Objectinternally, but since the whole thing is wrapped in a block expression, this never collides with otherobj!calls elsewhere in your code. You just can't refer to that type by name from outside the block — if you need that, uselet_obj!instead.
infer_obj! — anonymous, type-inferred objects
infer_obj! is like obj!, but you skip writing out field types entirely. Each field becomes its own generic type parameter on the generated struct, and Rust infers the concrete type from the value you assign. This is handy for quick, throwaway objects where explicit types would just be noise.
Syntax
infer_obj! {
[#[attributes]]
field_name: value,
...
}
- Fields are comma-separated
name: valuepairs. A trailing comma is optional, and the field list may be empty. - Optional attributes may precede the field list.
Examples
use infer_obj;
let mixed = infer_obj! ;
assert_eq!;
assert_eq!;
assert_eq!;
Objects can be nested, since each field's value is just an ordinary expression:
use infer_obj;
let complex = infer_obj! ;
assert_eq!;
assert_eq!;
An empty object is valid too:
use infer_obj;
let empty = infer_obj! ;
drop;
Note:
infer_obj!does not supportimplblocks — because the generated struct is generic over every field's type, implementing a trait or method on it would require naming those generic parameters yourself, which defeats the purpose of the macro. If you need behavior attached to the object, reach forobj!orlet_obj!instead. Also note that each field name doubles as a generic type parameter name, so field names must be valid, unique identifiers.
let_obj! — named objects bound to a variable
let_obj! declares a real, nameable struct and binds an instance of it to a local variable — with the same name as the type — in a single step. Unlike obj! and infer_obj!, the generated type is left in scope, so you can construct additional instances of it later using normal struct-literal syntax.
Syntax
let_obj! {
[#[attributes]]
[mut] TypeName {
field_name: FieldType = value,
...
}
[impl [Trait] { .. }]...
}
TypeNamebecomes both the struct's type name and the bound variable's name.- The optional
mutkeyword makes the variable mutable. - Fields are comma-separated
name: Type = valuepairs, with an optional trailing comma. The field list may be empty. - Optional attributes may precede
TypeNameand are forwarded to the struct definition. - Zero or more
impl [Trait] { .. }blocks may follow the field list.
Examples
Immutable object:
use let_obj;
let_obj! ;
assert_eq!;
assert_eq!;
Mutable object:
use let_obj;
let_obj! ;
assert_eq!;
Counter.count += 5;
assert_eq!;
Implementing a trait, then reusing the generated type by name:
use let_obj;
let_obj! ;
assert_eq!;
// `Bot` the type is still in scope, so it can be constructed again
// explicitly, just like any other struct.
let another_bot: Bot = Bot ;
assert_eq!;
An empty object is valid too:
use let_obj;
let_obj! ;
let _copy = EmptyObj;
Note: because the generated variable shares its name with the generated type,
let_obj!applies#[allow(non_camel_case_types)]and#[allow(non_snake_case)]internally, so using a conventionalPascalCasetype name as a variable won't trigger lint warnings.
Choosing the right macro
- Need a quick, throwaway value and don't care about the type's name? Use
obj!(orinfer_obj!if you don't want to write out field types). - Need to attach a trait implementation or methods, but only within a single expression/block? Use
obj!. - Need the type itself to be reusable — as a function parameter, return type, or to construct more instances later? Use
let_obj!. - Want the absolute least ceremony, with field types inferred instead of written out? Use
infer_obj!.
Attributes and derives
All three macros forward attributes placed before the field list onto the generated struct, so standard derives work as expected:
use obj;
let tagged = obj! ;
assert_eq!;
Implementing traits and methods
Both obj! and let_obj! support trailing impl blocks:
impl { .. }implements inherent methods directly on the generated object.impl SomeTrait { .. }implementsSomeTraitfor the generated object.
infer_obj! does not support impl blocks, since its generated struct is generic over every field's type (see above).
Limitations
obj!andinfer_obj!types are not nameable. Since both expand to a struct namedObjectinside a block expression, you cannot refer to that exact type from outside the macro invocation. Uselet_obj!if you need a type you can name elsewhere (as a function signature, stored field, etc.).infer_obj!field names must be valid generic parameter identifiers, since each field name is reused as a generic type parameter on the generated struct.infer_obj!cannot implement traits or methods, for the same generic-parameter reason described above.- These macros generate real structs at the call site, so normal Rust visibility, ownership, and lifetime rules apply — there is no dynamic typing or reflection involved.
License
Licensed under the Apache License and the MIT License. See LICENSE-APACHE2.txt and LICENSE-MIT.txt for details.