borrowize 0.3.0

Derive borrowed view structs from owned Rust structs.
Documentation

borrowize

crates.io docs.rs

borrowize provides #[derive(View)], a small procedural macro for generating borrowed view types from owned structs and enums.

The macro is useful when an API wants to expose a cheap borrowed projection of an owned value without writing a second struct and conversion method by hand.

Quick Start

use borrowize::View;

#[derive(Debug, PartialEq, Eq)]
struct Tag(&'static str);

#[derive(View)]
struct User {
    id: u64,
    name: String,
    tags: Vec<Tag>,
}

let user = User {
    id: 7,
    name: "Ada".to_owned(),
    tags: vec![Tag("admin"), Tag("editor")],
};

let view = user.view();

assert_eq!(*view.id, 7);
assert_eq!(view.name, "Ada");
assert_eq!(view.tags, [Tag("admin"), Tag("editor")]);

This generates a sibling view type similar to:

struct UserView<'borrowize> {
    id: &'borrowize u64,
    name: &'borrowize str,
    tags: &'borrowize [Tag],
}

impl User {
    fn view(&self) -> UserView<'_> {
        UserView {
            id: &self.id,
            name: self.name.as_str(),
            tags: self.tags.as_slice(),
        }
    }
}

The generated lifetime is always named 'borrowize inside generated view type definitions. Source structs may not define a lifetime with that name.

Default Mapping

borrowize maps common owned field types to borrowed forms:

Source field type Generated view field type Generated expression
String &'borrowize str self.field.as_str()
Vec<T> &'borrowize [T] self.field.as_slice()
[T; N] &'borrowize [T] &self.field[..]
Box<T> &'borrowize T self.field.as_ref()
PathBuf &'borrowize std::path::Path self.field.as_path()
Option<T> Option<Borrowed<T>> self.field.as_ref().map(...)
&'a T &'a T self.field
other T &'borrowize T &self.field

Option<T> is mapped recursively, so Option<String> becomes Option<&'borrowize str> and Option<u64> becomes Option<&'borrowize u64>.

Type matching is syntactic. The macro recognises unqualified names and common standard-library qualified names such as std::string::String, alloc::string::String, std::vec::Vec, alloc::vec::Vec, std::option::Option, core::option::Option, std::boxed::Box, alloc::boxed::Box, and std::path::PathBuf. Type aliases are treated as unknown types and are borrowed as &T.

Struct And Enum Options

Item-level options are written as #[borrowize(...)] on the source struct or enum. Names are explicit; there are no shorthand options.

use borrowize::View;

#[derive(View)]
#[borrowize(
    view_name = "UserParts",
    view_visibility = "pub",
    field_visibility = "pub",
    method_name = "as_parts",
    method_visibility = "pub"
)]
pub struct User {
    id: u64,
    name: String,
}

Supported item options:

Option Meaning
view_name = "Name" Override the generated view type name.
view_visibility = "pub" Override the generated view type visibility.
field_visibility = "pub" Set the default visibility for generated view fields.
method_name = "name" Override the generated method name.
method_visibility = "pub" Override the generated method visibility.
no_method Do not generate the inherent view method.

When a visibility option is omitted, visibility is inherited from the source item or field. Explicit visibility values must be legal Rust visibility syntax, such as pub, pub(crate), or pub(super).

no_method cannot be combined with method_name or method_visibility.

field_visibility applies to generated named and tuple struct view fields only. It cannot be used on enums because Rust enum variant fields do not have independent visibility.

Enums

Enums with named-field variants, tuple variants, and unit variants are supported. Each variant is mapped independently using the same default field mapping as structs.

use borrowize::View;

#[derive(View)]
enum Event {
    Created {
        id: u64,
        name: String,
    },
    Empty,
}

let event = Event::Created {
    id: 7,
    name: "Ada".to_owned(),
};

let view = event.view();

match view {
    EventView::Created { id, name } => {
        assert_eq!(*id, 7);
        assert_eq!(name, "Ada");
    }
    EventView::Empty => unreachable!(),
}

Tuple enum variant fields are exposed to generation_expression overrides as field_0, field_1, and so on.

Field Options

Field-level options are written as #[borrowize(...)] on a source field.

use borrowize::View;

#[derive(View)]
struct Encoded {
    #[borrowize(
        borrowed_type = "&'borrowize str",
        generation_expression = "::std::str::from_utf8(&self.bytes).expect(\"valid utf8\")"
    )]
    bytes: Vec<u8>,
}

Supported field options:

Option Meaning
borrowed_type = "Type" Override the generated view field type.
generation_expression = "expr" Override the generated field initializer in the view method.
visibility = "pub" Override this generated struct view field's visibility.

borrowed_type overrides may refer to the generated borrow lifetime as 'borrowize.

generation_expression is parsed as a Rust expression in the generated method body. On structs it can refer to self because the generated method borrows &self, including tuple fields such as self.0. On named enum variants it can refer to the named variant field binding. On tuple enum variants it can refer to field_0, field_1, and later generated field bindings.

Manual View Construction

Generated view fields inherit source field visibility by default. Use field_visibility or field-level visibility when views need to be assembled outside the source module.

use borrowize::View;

#[derive(View)]
#[borrowize(view_visibility = "pub", field_visibility = "pub", no_method)]
pub struct PublicSource {
    id: u64,
    name: String,
}

let id = 21;
let view = PublicSourceView {
    id: &id,
    name: "manual",
};

This is useful when you have borrowed pieces available before you have an owned source value to call view() on.

Generics

Source generics, const generics, lifetimes, and where clauses are preserved on the generated view type and inherent method.

use borrowize::View;

#[derive(View)]
struct Generic<'a, T, const N: usize>
where
    T: 'a,
{
    name: String,
    items: Vec<T>,
    borrowed: &'a T,
    fixed: [u8; N],
}

Limitations

View supports named-field structs, tuple structs, named-field enum variants, tuple enum variants, and unit enum variants. Unions and unit structs produce compile errors.

The generated inherent method can conflict with a user-defined method of the same name. Use method_name to choose a different method name or no_method when only the view type is needed.

Nested custom view conversion is not inferred. Unknown types are borrowed as &T, even if they also derive View.

Type matching is syntactic, so aliases and re-exports of recognised container types are not expanded.

The generated lifetime name 'borrowize is reserved.

Licence

borrowize is distributed under the terms of both the MIT licence and the Apache License, Version 2.0. You may use it under either licence, at your option.

See LICENSE-MIT and LICENSE-APACHE for the full licence texts.

Contributions

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in borrowize is licensed as MIT OR Apache-2.0, without any additional terms or conditions.