Taylor Cramer
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee
  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    1
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # In-place initializion via outptrs ## Prior work - [@alicerhyl's In-place initialization](https://hackmd.io/@aliceryhl/BJutRcPblx), the corresponding [lang experiment proposal](https://github.com/rust-lang/lang-team/issues/336), and [this Zulip discussion thread](https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/In-place.20initialization/near/522444283). - Rust-for-Linux's [`pin_init` crate](https://github.com/Rust-for-Linux/pin-init) - [`moveit::New` trait](https://docs.rs/moveit/latest/moveit/new/trait.New.html) and its descendent, [Crubit's `Ctor` trait](https://github.com/google/crubit/blob/c65afa7b2923a2d4c9528f16f7bfd4aef6c80b86/support/ctor.rs#L189-L226) ## The limitations of `-> impl Init` [@aliceryhl's In-place initialization](https://hackmd.io/@aliceryhl/BJutRcPblx) proposal includes this (simplified) trait: ```rust unsafe trait Init<T> { type Error; unsafe fn init(self, slot: *mut T) -> Result<(), Self::Error>; } // Note: references to `PinInit` have been normalized to `Init` // in this doc in order to avoid confusion. ``` and this example: ```rust struct MyStruct { inner: bindgen::some_c_struct, } impl MyStruct { fn new(name: &str) -> impl Init<MyStruct, Error> { init MyStruct { inner: unsafe { core::init::zeroed() }, _: { let ret = unsafe { bindgen::init_some_c_string(&mut inner, name) }; if ret < 0 { Err(Error::from_errno(ret)) } else { // Result<(), Error> is an initializer // for (). Ok(()) } }, } } } ``` The fundamental approach, shared with other ecosystem libraries, relies on returning an `-> impl Init` value that knows how to initialize the output at some later-provided `*mut T` slot. This delayed application / currying of the output pointer has two significant advantages: 1. The author and caller of `MyStruct::new` can write safe (or mostly-safe) code, leaving the unsafe pointer writes to the library code that runs the delayed `init(slot)` function. 2. The end-user API has a similar shape to regular Rust code: outputs are return values, not output arguments. However, this approach has some limitations. ### Control flow Similar to other `-> impl Trait` delayed bodies such as closures, coroutines, and `async` blocks, code inside an `impl Init` expression cannot `break`/`continue`/`return` as normal. That is, one cannot write the following code: ```rust impl MyStruct { fn new(name: &str) -> Result<impl Init<MyStruct>, Error> { Ok(init MyStruct { inner: unsafe { core::init::zeroed() }, _: { let ret = unsafe { bindgen::init_some_c_string(&mut inner, name) }; if ret < 0 { return Err(Error::from_errno(ret)); } }, }) } } ``` The `return Err` above cannot return a `Result` from the top-level function because the `init` body is only ever executed after the function has returned and `Init::init` is invoked. `impl Init` is created *before* ever actually running any code inside the `init` body. This is why the `Init` trait definition above includes a built-in error type: common usage patterns require the ability to fail midway through initialization, after an address has been chosen. Building in `Result` addresses this common issue but does not address the more general problem of control flow which travels in and out of initialization expressions, or which simply happens *prior* to the initialization expression. Other return types such as `-> Option<impl Init>` or `Either<impl Init<A>, impl Init<B>>` cannot be expressed and must be "smuggled" through a `Result` return type. For example, the following code has two separate error conditions: one pre-initializer and one mid-initializer. Each must be handled separately: ```rust impl MyStruct { fn new(name: &str) -> Result<impl Init<MyStruct, Error=Error>, Error> { if name.starts_with("bad") { return Err(...); } Ok(init MyStruct { inner: unsafe { core::init::zeroed() } _: { let ret = unsafe { bindgen::init_some_c_string(&mut inner, name) }; if ret < 0 { return Err(Error::from_errno(ret)); } Ok(()) }) } } ``` This is confusing and forces the end user to consider too many details about where exactly an error may occur. Additionally, users must understand that `init` expressions (like closures, coroutines, and async blocks, but unlike other struct expressions) capture `return` rather than allowing `return` to return out of the top-level function. ### Composition The `impl Init` proposal offers some mechanisms for composing initializers such as `(impl Init<A>, impl Init<B>) -> impl Init<(A, B)>` via the `init` syntax: ```rust fn make_pair<A, B, E>( a_init: impl Init<A, Error=E>, b_init: impl Init<B, Error=E>, ) -> impl Init<(A, B), Error=E> { init (a_init, b_init) } ``` However, as in the `Result` section above, this composition builds in: - a particular execution order of `a_init` and `b_init` - early-return on error - a requirement that the error types match, implying a need for `Init::map_err` or similar - generics: the `init` functions cannot be fn pointers or `dyn` trait methods because they return some `impl Init` type of unknown size ### `dyn` safety As with other `-> impl Trait` APIs, `-> impl Init` results in an unnameable return type of unspecified size, and so is not `dyn`-safe. The linked proposal provides a means for addressing the general problem of `-> impl Trait` in traits via vtable-stored `Layout`s, but `-> impl Init` functions themselves would still require the caller to dynamically allocate space to store the `init` captures. This intermediate allocation of `init` captures is unnecessary given that the `init` body itself will be executed immediately afterwards when provided with the out-pointer. ## The alternative: out pointers Here's what our original example might look like using an out-pointer-based approach: ```rust impl MyStruct { fn new<'o>( name: &str, out: Uninit<'o, MyStruct>, ) -> Result<InPlace<'o, MyStruct>, Error> { let res = unsafe { bindgen::init_some_c_string(&raw out.inner, name) }; if ret < 0 { return Err(Error::from_errno(ret)); } Ok(unsafe { out.assume_init() }) } } ``` compared to the original: ```rust impl MyStruct { fn new(name: &str) -> impl Init<MyStruct, Error> { init MyStruct { inner: unsafe { core::init::zeroed() }, _: { let ret = unsafe { bindgen::init_some_c_string(&mut inner, name) }; if ret < 0 { Err(Error::from_errno(ret)) } else { // Result<(), Error> is an initializer // for (). Ok(()) } }, } } } ``` A few things to notice: - The function has grown an extra parameter, `outptr: Uninit<'o, MyStruct>`. - The `Result` is now top-level, and control-flow is uninterrupted. - We've replaced our `-> impl Init<MyStruct, Error=Error>` return type with a concrete return type, `Result<InPlace<'o, MyStruct>, Error>`. We can also write infallible initialization examples by removing the `Result`: ```rust impl MyStruct { fn new<'o>( name: &str, outptr: Uninit<'o, MyStruct>, ) -> InPlace<'o, MyStruct> { unsafe { init_some_c_string(&raw out.inner, name); outptr.assume_init() } } } ``` Or we can wrap it in an `Option`: ```rust impl MyStruct { fn new<'o>( name: &str, outptr: Uninit<'o, MyStruct>, ) -> Option<InPlace<'o, MyStruct>> { if !unsafe { init_some_c_string(&raw out.inner, name) } { return None; } Some(unsafe { out.assume_init() }) } } ``` or add a retry loop / rerun initialization, or any similar pattern. ## `Uninit<'o, T>` and `InPlace<'o, T>` This proposal would introduce two new library types, `Uninit` and `InPlace`. [See this playground for a minimal usable API](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=dbfa54594ee546b266ce4a189e2b5743). `Uninit` is a wrapper around `*mut MaybeUninit<T>` with an extra lifetime parameter,`'o`, which is tied to the output location. The above examples also assume that it implements `DerefRaw` or some similar operation so that the syntax `&raw out.field_of_t` is valid. It provides the following API: ```rust impl<'o, T> Uninit<'o, T> { unsafe fn from_raw<'o>(raw: *mut T) -> Self; fn as_ptr(&self) -> *const T; fn as_mut_ptr(&mut self) -> *mut T; fn write(self, val: T) -> InPlace<'o, T>; unsafe fn assume_init(self) -> InPlace<'o, T>; } ``` `InPlace<'o, T>` is a `Box<T>` whose deallocator is a no-op, as it only refers to stack-allocated data (or heap-allocated data whose allocation is externally managed): ```rust type InPlace<'o, T> = Box<T, A=InPlaceAlloc<'o>>; struct InPlaceAlloc<'o> { // Force `'o` to be invariant. // // This ensures that an `InPlaceAlloc<'o>` can only be created // by initializing the corresponding `Uninit<'o>`. marker: PhantomData<fn(&'o ()) -> &'o ()> // See "Pinning" below for details. initialized_without_drop: *mut bool, } // No-op `Allocator` implementation. impl<'o> Allocator for InPlaceAlloc<'o> { fn allocate(&self, _: Layout) -> Result<NonNull<[u8]>>, AllocError> { AllocError } unsafe fn deallocate(&self, _: NonNull<u8>, _: Layout) { *initialized_without_drop = false; } } ``` If dropped, the `InPlace<'o, T>` will destroy the underlying `T`, but will not deallocate the memory in which the `T` was stored. This is similar to previously-proposed `&own`, and can be used to provide the same behavior. See [this playground example](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=7069dedca298afb85c0dca5e68bb2804): ```rust fn takes_fnonce_noheap(f: InPlace<dyn FnOnce() + '_>) { f() } fn main() { let x = "fooey".to_string(); let f = in_place!(|| println!("{}", x)); takes_fnonce_noheap(f); } ``` ## Composition and fieldwise-initialization The earlier `init MyStruct { ... }` proposal allows the values of fields to be provided as initializers (`impl Init<FieldTy>` values). However, this prevents field initialization from depending on the values or addresses of other fields or else requires that prior field names are added to the scope of later field expressions (the current proposal). It also bakes error / early-return behavior into the struct expression itself. The first of these issues, fields depending on one another, could be resolved in an outptr-based solution. We can use a derive-based projection similar to what is available in `pin_project` to turn an `Uninit<Struct>` into `(Uninit<FieldOneTy>, Uninit<FieldTwoTy>` etc., and then map the resulting `(InPlace<FieldOneTy>, InPlace<FieldTwoTy>)` back into an `InPlace<Struct>`: ```rust fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct> { MyStruct::fieldwise_init(out, |x, y| { let x_out = make_x_in_place(x); // Field `y` can depend on the address of field `x`. let y_out = y.write(x.as_ptr()); (x_out, y_out) }) } // autogenerated: impl MyStruct { pub fn fieldwise_init( out: Uninit<'_, MyStruct>, f: impl for<'ox, 'oy> FnOnce( Uninit<'ox, XType>, Uninit<'oy, YType>, ) -> (InPlace<'ox, XType>, InPlace<'oy, YType>), ) -> InPlace<'_, MyStruct> { ... } } ``` Unlike the earlier `-> impl Init` proposal, this allows fields to be initialized or reinitialized in any order, and fields can access each others addresses or (initialized) values during construction. The downside of this approach is that wrapping in a closure limits control flow within initialization similar to the `-> impl Init` approaches. Users would need `try_` variants. ## Gradual struct initialization without closures We can break out of the closure approach and allow a higher level of control flow by defining a macro or similar tool which creates a builder structure paired with particular outpointers via unique per-field lifetimes. Because each use of the macro results in unique per-field lifetimes, we know that the outptrs handed back to us refer to fields of the original type. ```rust fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct> { let (builder, x_out, y_out) = build_fields_of_my_struct!(out); // Local types: // x_out: Uninit<'anon_x, X> // y_out: Uninit<'anon_y, Y> // builder: FnOnce( // InPlace<'anon_x, X>, // InPlace<'anon_y, Y> // ) -> InPlace<'_, MyStruct> let x_out = make_x_in_place(x); // Field `y` can depend on the address of field `x`. let y_out = y.write(x.as_ptr()); builder(x_out, y_out) } ``` This, too, can be accomplished without adding any new language features. ## Initialization inside containers ### Using `new_with` One option would be for `Box` and other containers (e.g. `Vec`, `[T; N]`) to gain new constructor functions called `new_with`: ```rust impl<T> Box<T> { fn new_with<F>(f: F) -> Self where F: impl for<'o> FnOnce(Uninit<'o, T>) -> InPlace<'o, T>; } ``` Usage example: ```rust // Given an in-place constructor function like this: fn make_mytype(_: Uninit<'_, MyType>, some_int: usize) -> InPlace<'_, MyType> { ... } // We can construct a `Box<MyType>` like this: let b: Box<MyType> = Box::new_with(|out| make_mytype(out, 27));` ``` Similarly, `Box::try_new_with(|out| try_make_mytype(out, 27))?` could be used for in-place constructor functions. However, this would result in an explosion of new APIs-- every construction API would need to grow a new `_with` variant, and the `try_` versions compose poorly with existing `try_`-based APIs which are fallible for other reasons (e.g. `Box::try_new_in`, which fails due to allocation errors). ### Using gradual initialization for `Box` One alternative, especially for `Box`, would be to allow gradual initialization through `Box<MaybeUninit<T>>` values: ```rust fn make_x_in_place(out: Uninit<'_, X>) -> InPlace<'_, X> { ... } // with non-native gradual initialization: // This assumes some `box_into_builder!` which turns // `Box<MaybeUninit<T>>` into // box_builder: impl FnOnce(InPlace<'o, T>) -> Box<T> // out_uninit: Uninit<'o, T> fn make_my_struct() -> Box<MyStruct> { let out = Box::<MyStruct>::new_uninit(); let (box_builder, out_uninit) = box_into_builder!(out); let (my_struct_builder, x_uninit, y_uninit) = my_struct_builder!(out); let x_out = make_x_in_place(x_uninit); let y_out = y_uninit.write(x_out.as_ptr()); box_builder.build(my_struct_builder.build(x_out, y_out)) } ``` ## Pinning Properly supporting `Pin<InPlace<'_, T>>` requires that the contents of the `InPlace` *must* be dropped before the corresponding memory is deallocated. Other (`'static`) allocators used with `Pin<Box<T>>` accomplish this by simply leaking memory: `mem::forget(some_box)` will never deallocate, so it's not a problem that the `T` is never dropped. However, scoped / non-`'static` allocators must ensure that all `Pin<Box<T, NonStaticA>>` drop their `T` before the allocator goes out of scope. It isn't obvious how to statically ensure this. However, it is possible to dynamically check. Each creator of an `Uninit` can keep a drop flag indicating whether the value has been initialized and not dropped. If code in scope creates an `InPlace<T>` and then `mem::forget`s it, we can panic when exiting the scope at which the `Uninit` was created: ```rust fn forget_pinned_struct() { let out = MaybeUninit::<Foo>::uninit(); // `uninit_into_builder` creates a super-let temporary which tracks // the initialization state of the `foo`. let (box_builder, foo_uninit) = uninit_into_builder!(out); `foo_uninit.set` sets the let pinned: Pin<InPlace<'_, Foo>> = foo_uninit.set(Foo { ... }); ... mem::forget(pinned); // BAD // PANIC when the temporary from `uninit_into_builder` // is dropped: `foo` is initialized but was not // dropped or moved-from. } ``` This will require that `Uninit` and `InPlace` allocators contain a pointer to their corresponding drop flags. Note that this is not necessary for zero-sized types, as there is no underlying memory. `Allocator::deallocate` is not even invoked when `Box<Zst>` is dropped. ## Ergonomics extension: native gradual initialization All APIs above can be implemented using existing language features (though they internally rely on existing `allocator_api` and `super let` support). One optional addition that could improve ergonomics would be to support native gradual initialization of structures (see [this partial initialization proposal](https://github.com/rust-lang/rust/issues/54987) and [this draft PR](https://github.com/rust-lang/rust/pull/143625)). This would be a significant benefit over both `init` and the closure or builder-based patterns above. Additionally, this could benefit non-in-place initialization that exists today. Rust already allows delayed initialization of locals, allowing for locals to depend on one another's value or address: ```rust let x; let y; x = 5; y = &raw const x; ``` However, the same thing is not currently possible when working with a struct: ```rust struct MyStruct { x: i32, y: *const i32 } let my_struct: MyStruct; my_struct.x = 5; // ERROR: partial initialization isn't supported my_struct.y = &raw const my_struct.x; ``` Nor when working with a value behind an uninit pointer: ```rust fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct> { out.x = 5; // ERROR } ``` What we'd like is for the fields of a struct behind an `Uninit<'_, MyStruct>` to be gradually-initializable in the same fashion as locals, and for the resulting value to be convertible to an `InPlace<'_, MyStruct>` once all fields have been initialized: ```rust fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct> { out.x = 5; out.y = &raw const out.x; declare_init!(out) } ``` The `declare_init!` macro would require that all fields have been initialized and would convert an `Uninit<'_, T>` to an `InPlace<'_, T>`. `declare_init!` could also be omitted by adding a coercion from a fully-initialized `Uninit<'_, T>` into an `InPlace<'_, T>`: ```rust fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct> { out.x = 5; out.y = &raw const out.x; out } ``` #### `Drop` glue One thing to note is that the `Drop` impl for `MyStruct` would not run until `declare_init!` is run. This is consistent with the behavior of early-exiting before a struct initialization expression has completed. The more complex thing to consider is when to drop the fields of a partially-initialized `Uninit`. This doc proposes that all fields of a single struct must be initialized in the same body. If a field is initialized but the parent structure is not passed to `declare_init!`, the fields which have been initialized will be dropped. This requires the compiler to track per-field drop flags, but this is already supported in the compiler today in order to allow [partially-moved-from structs](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=51bbaed3f9ec43a61892ed18c0fe70b2). Cross-body initialization of a struct is not allowed. Notably, the following will not work: ```rust fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct> { out.x = String::from("foo"); // ERROR: `out` is partially initialized set_y_and_init(out) } ``` #### Composition using native gradual initialization With gradual initialization, in-place construction functions could be composed by using a new `&uninit` expression to create an `Uninit<MyField>` from an `Uninit<MyStruct>`: ```rust fn make_x_in_place(out: Uninit<'_, X>) -> InPlace<'_, X> { ... } fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct> { out.x = make_x_in_place(&uninit out.x); out.y = &raw const out.x; out } ``` `&uninit out.x` creates a new `Uninit<X>` with a unique lifetime. `out.x` can then be marked as initialized by assigning an `InPlace<X>` with the matching lifetime. This assignment has no runtime behavior, but merely marks the local as having been initialized. Besides enforcing uniqueness, an `uninit` borrow behaves similar to an exclusive `&mut` reference: only one borrow can be made a time. ## Ergonomics extension: in-place return values `fn make_my_struct(out: Uninit<'_, MyStruct>) -> InPlace<'_, MyStruct>` is still visually cluttered and much harder to read than `fn make_my_struct() -> impl Init<Output = MyStruct>`. We could consider providing sugar like the following: ```rust fn make_my_struct() -> in_place { out: MyStruct } { out.x = make_x_in_place(&uninit out.x); out.y = &raw const out.x; out } ``` This would implicitly create a separate argument for every name that appears in an `in_place { ... }` brace in the return type. The downside of this approach is that it introduces novel syntax users have to learn. Additionally, we would need to figure out how the values are bound at the call site. Named arguments make this easy, but that implies yet another language feature: ```rust let my_struct: MyStruct; my_struct = make_my_struct(out = &uninit my_struct); ```

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully