# structecs
[](https://crates.io/crates/structecs)
[](https://docs.rs/structecs)
[](https://github.com/moriyoshi-kasuga/structecs/blob/main/LICENSE)
[](https://www.rust-lang.org)
**A structural data access framework for Rust.**
Access your data structures with OOP-like ergonomics, manage them however you want.
---
## ⚠️ Development Status
This crate is currently under active development and undergoing major refactoring.
**Version 0.3.x is a complete breaking change from earlier versions:**
- Removed: `World`, `Archetype`, `EntityID` (centralized ECS management)
- Focus: Structural type access with user-managed collections
The API is not stable and may change significantly. Use at your own risk. Feedback and contributions are welcome!
---
## What is structecs?
`structecs` is a structural data access framework that lets you work with nested data structures using type-safe extraction and smart pointers.
Instead of managing entities in a central ECS World, structecs gives you the tools to build your own data management patterns:
- **`Acquirable<T>`** - Arc-based smart pointers for shared ownership
- **`Extractable`** - Derive macro for structural type extraction from nested types
- **Type-safe extraction** - Access nested components through their parent structures
You manage your data structures (HashMap, BTree, custom collections) however you want. structecs just makes accessing nested types ergonomic and type-safe.
## Motivation
This framework was created for building a Minecraft server in Rust, where:
- Entity relationships are complex (Player ⊂ LivingEntity ⊂ Entity)
- Game logic is too varied to fit into rigid Systems
- OOP patterns are familiar but Rust's ownership makes traditional OOP difficult
**Why no World/Archetype/EntityID?**
Early versions included ECS-style centralized management, but this was removed because:
1. **No global entity tracking needed** - In a Minecraft server, entities are managed per-Chunk or per-World
2. **Arc wrapping makes centralized management redundant** - Since `Acquirable` uses `Arc` internally, you can store and share references however you want
3. **User-defined organization is better** - Different use cases need different organization:
- `Arc<RwLock<HashMap<UUID, Acquirable<Entity>>>>` for entities
- `Arc<RwLock<HashMap<BlockPos, Acquirable<Block>>>>` for blocks
- Chunk-local collections, World-level collections, etc.
structecs provides the primitives (`Acquirable`, `Extractable`). You build the architecture.
## Core Concepts
### `Acquirable<T>` - Smart Pointers with Shared Ownership
`Acquirable<T>` is an Arc-based smart pointer that allows shared ownership of data with transparent access through `Deref`.
```rust
use structecs::*;
#[derive(Extractable)]
struct Player {
name: String,
health: u32,
}
let player = Acquirable::new(Player {
name: "Steve".to_string(),
health: 100,
});
// Access through Deref
println!("Player: {}, Health: {}", player.name, player.health);
// Clone creates a new reference to the same data
let player_ref = player.clone();
assert!(player.ptr_eq(&player_ref));
```
### `WeakAcquirable<T>` - Weak References
Prevent circular references and implement cache-like structures with weak references:
```rust
use structecs::*;
#[derive(Extractable)]
struct Player {
name: String,
health: u32,
}
let player = Acquirable::new(Player { name: "Alex".to_string(), health: 80 });
let weak = player.downgrade();
// Upgrade when needed
if let Some(player_ref) = weak.upgrade() {
println!("Player still alive: {}", player_ref.name);
}
```
### `Extractable` - Structural Type Extraction
The `Extractable` derive macro enables type-safe extraction of nested components:
```rust
use structecs::*;
#[derive(Extractable)]
struct Health {
current: u32,
max: u32,
}
#[derive(Extractable)]
#[extractable(health)] // Mark nested Extractable fields
struct LivingEntity {
id: u32,
health: Health,
}
#[derive(Extractable)]
#[extractable(living)]
struct Player {
name: String,
living: LivingEntity,
}
let player = Acquirable::new(Player {
name: "Steve".to_string(),
living: LivingEntity {
id: 42,
health: Health { current: 80, max: 100 },
},
});
// Extract nested types
let health: Acquirable<Health> = player.extract::<Health>().unwrap();
assert_eq!(health.current, 80);
let living: Acquirable<LivingEntity> = player.extract::<LivingEntity>().unwrap();
assert_eq!(living.id, 42);
```
## Design Philosophy
- **No centralized storage** - You manage your own collections and data structures
- **OOP-like structural access** - Access nested types through parent structures naturally
- **User-controlled concurrency** - Wrap your collections in `Arc<RwLock<...>>` as needed
- **Type-safe extraction** - The derive macro ensures compile-time safety for nested type access
- **Minimal runtime overhead** - Offset-based extraction with zero-cost abstractions
## Compile-time Safety
structecs provides two levels of type checking:
### Runtime Checking (Default)
```rust
use structecs::*;
#[derive(Extractable)]
struct Entity { id: u32 }
#[derive(Extractable)]
#[extractable(entity)]
struct Player { name: String, entity: Entity }
let player = Acquirable::new(Player {
name: "Steve".to_string(),
entity: Entity { id: 1 },
});
// Returns Option - safe runtime check
let entity: Option<Acquirable<Entity>> = player.extract::<Entity>();
assert!(entity.is_some());
```
### Compile-time Checking (Checked APIs)
For performance-critical paths, use `_checked` variants that validate at compile time:
```rust
use structecs::*;
#[derive(Extractable)]
struct Entity { id: u32 }
#[derive(Extractable)]
#[extractable(entity)]
struct Player { name: String, entity: Entity }
// Compile-time validation - panics at compile time if Player doesn't contain Entity
let player: Acquirable<Player> = Acquirable::new_checked(Player {
name: "Steve".to_string(),
entity: Entity { id: 1 },
});
// No runtime Option check needed - guaranteed to succeed
let entity: Acquirable<Entity> = player.extract_checked::<Entity>();
```
**How it works:**
- `ExtractionMetadata::is_has<Container, Target>()` runs at compile time (const evaluation)
- Uses string-based type identification (`module_path!()` + type name)
- Why not `TypeId`? Because `TypeId::eq()` is not yet const-stable in Rust
- Debug builds panic at compile time, release builds use `unsafe` for zero cost
## Optional Archetype (Feature Flag)
For common use cases, structecs provides an optional `Archetype<Key, Base>` collection:
```rust
use structecs::{Archetype, Extractable, Acquirable};
#[derive(Extractable)]
struct Entity { id: u32 }
#[derive(Extractable)]
#[extractable(entity)]
struct Player { name: String, entity: Entity }
// Compile-time checked: can only insert types containing Entity
let entities: Archetype<u32, Entity> = Archetype::default();
let player = Player {
name: "Alice".to_string(),
entity: Entity { id: 1 },
};
// Stores as Acquirable<Entity>, but accepts any U containing Entity
entities.insert(1, player);
// Retrieve as base type
let entity = entities.get(&1).unwrap();
// Extract back to specific type
let player_ref = entity.extract::<Player>().unwrap();
assert_eq!(player_ref.name, "Alice");
```
**Key Features:**
- **Thread-safe**: `Clone` (cheap Arc clone) + `Send + Sync`
- **Compile-time validated**: `insert()` requires `U: contains Base`
- **Minimal API**: Access `inner()` for custom operations; methods added only when needed
- **Type flexibility**: Stores as `Acquirable<Base>`, extract to specific types
**Enable with:**
```toml
[dependencies]
structecs = { version = "0.3", features = ["archetype"] }
```
**Design Philosophy:**
`Archetype` is intentionally minimal. For custom operations, use:
```rust
use structecs::*;
#[derive(Extractable)]
struct Entity { id: u32 }
let entities: Archetype<u32, Entity> = Archetype::default();
// Access the underlying Arc<RwLock<HashMap>> for custom operations
let map = entities.read(); // or .write() for mutations
// Custom iteration, filtering, etc.
```
## Usage Examples
### Basic Example
```rust
use structecs::*;
#[derive(Extractable)]
struct Entity {
id: u32,
}
#[derive(Extractable)]
#[extractable(entity)]
struct NamedEntity {
name: String,
entity: Entity,
}
#[derive(Extractable)]
#[extractable(entity)]
struct BlockEntity {
block_type: String,
entity: Entity,
}
let named = Acquirable::new(NamedEntity {
name: "Steve".to_string(),
entity: Entity { id: 42 },
});
let block = Acquirable::new(BlockEntity {
block_type: "Stone".to_string(),
entity: Entity { id: 43 },
});
let entities: Vec<Acquirable<Entity>> = vec![named.extract().unwrap(), block.extract().unwrap()];
for entity in entities {
println!("Entity ID: {}", entity.id);
}
```
The key insight: **You decide how to organize your data**. Per-chunk HashMap? Per-world BTreeMap? Custom spatial index? It's all up to you.
---
## Feature Flags
structecs provides optional features that can be enabled in your `Cargo.toml`:
| `archetype` | Provides `Archetype<Key, Base>` - a thread-safe, type-checked HashMap wrapper for storing entities by a common base type. Useful for quick prototyping or simple use cases. | ❌ Disabled |
**Example: Enabling features**
```toml
[dependencies]
# Enable the archetype feature
structecs = { version = "0.3", features = ["archetype"] }
# Or use default (no features)
structecs = "0.3"
```
**When to use `archetype`:**
- ✅ You want a pre-built collection for storing entities
- ✅ You need thread-safe access with `Arc<RwLock<HashMap>>`
- ✅ You're prototyping and don't want to build custom storage
**When NOT to use `archetype`:**
- ❌ You need custom storage structures (spatial indexes, quad-trees, etc.)
- ❌ You want full control over locking strategies
- ❌ You're building a specialized data management system
---
## Resources
- **[API Documentation](https://docs.rs/structecs)** - Full API reference
- **[Examples](examples/)** - Practical usage patterns
- **[Crates.io](https://crates.io/crates/structecs)** - Published versions
---
## License
Licensed under MIT License.
---
## Contributing
This project is in early development. Feedback, ideas, and contributions are welcome!
If you have suggestions or find issues, please open an issue or pull request on GitHub.