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
      • 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 Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync 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
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
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